From d7973dc09de27b301e83a25d3642578ad01df476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E9=B9=8F?= Date: Thu, 21 May 2026 20:21:08 +0800 Subject: [PATCH] Add EasyCode front workbench implementation plan --- .../2026-05-21-easycode-front-workbench.md | 1843 +++++++++++++++++ 1 file changed, 1843 insertions(+) create mode 100644 RuoYi-Vue/docs/superpowers/plans/2026-05-21-easycode-front-workbench.md diff --git a/RuoYi-Vue/docs/superpowers/plans/2026-05-21-easycode-front-workbench.md b/RuoYi-Vue/docs/superpowers/plans/2026-05-21-easycode-front-workbench.md new file mode 100644 index 0000000..a400122 --- /dev/null +++ b/RuoYi-Vue/docs/superpowers/plans/2026-05-21-easycode-front-workbench.md @@ -0,0 +1,1843 @@ +# EasyCode Front Workbench Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build an independent EasyCode front workbench where front users log in, generate editable MySQL table drafts through a backend DeepSeek service, preview backend/frontend/admin project files, and download generated source. + +**Architecture:** Add a standalone `easycode-web` Vue 3 + Vite app for public/front-user flows. Add `/front/**` APIs to `ruoyi-admin`, store front-user drafts in new `front_*` tables under `ruoyi-generator`, call DeepSeek only through backend `AiGenerateService`, and reuse the existing generator rendering logic for preview/download. + +**Tech Stack:** Spring Boot 2.5, Spring Security, MyBatis XML, Fastjson2, Velocity generator, Vue 3, Vite, Vue Router, Axios, Element Plus, Sass. + +--- + +## Scope Check + +This is a cross-cutting feature, but the pieces are sequential parts of one product workflow: + +1. Database/auth foundation. +2. Project draft and editable database design. +3. DeepSeek-backed database generation. +4. Generator preview/download adaptation. +5. Independent front app and end-to-end integration. + +Implement in the task order below. Each task should end with tests and a commit. + +## File Map + +### SQL + +- Create `sql/front_workbench.sql`: standalone DDL for new front-user and draft tables. +- Modify `sql/db.sql`: include the same DDL so a fresh database has the front workbench tables. + +### Common and Framework + +- Modify `ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java`: add login type constants. +- Modify `ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java`: add `loginType`. +- Modify `ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java`: prevent front tokens from authenticating non-`/front/**` requests, and prevent admin tokens from authenticating protected `/front/**` requests. +- Modify `ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java`: allow anonymous `/front/auth/login` and `/front/auth/register`. +- Modify `ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java`: mark admin logins as admin tokens. + +### Generator Module + +- Create domain classes under `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/`: + - `FrontUser.java` + - `FrontProject.java` + - `FrontProjectTable.java` + - `FrontProjectColumn.java` + - `FrontProjectGeneration.java` + - `FrontProjectFile.java` +- Create DTO classes under `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/`: + - `FrontLoginBody.java` + - `FrontRegisterBody.java` + - `FrontProjectCreateRequest.java` + - `FrontProjectUpdateRequest.java` + - `GenerateDatabaseRequest.java` + - `DatabaseDesignResponse.java` + - `DatabaseTableDesign.java` + - `DatabaseColumnDesign.java` + - `PreviewFileRequest.java` + - `ProjectStructureResponse.java` +- Create mappers under `ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/front/`: + - `FrontUserMapper.java` + - `FrontProjectMapper.java` + - `FrontProjectTableMapper.java` + - `FrontProjectColumnMapper.java` + - `FrontProjectGenerationMapper.java` + - `FrontProjectFileMapper.java` +- Create mapper XML under `ruoyi-generator/src/main/resources/mapper/front/` with matching names. +- Create services under `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/`: + - `IFrontUserService.java` + - `FrontUserServiceImpl.java` + - `IFrontProjectService.java` + - `FrontProjectServiceImpl.java` + - `IAiGenerateService.java` + - `AiGenerateServiceImpl.java` + - `IDeepSeekClient.java` + - `HttpDeepSeekClient.java` + - `AiDatabaseSchemaValidator.java` + - `MysqlDdlBuilder.java` + - `FrontProjectConverter.java` + - `IFrontProjectPreviewService.java` + - `FrontProjectPreviewServiceImpl.java` +- Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/config/`: + - Create `DeepSeekProperties.java`. +- Modify existing generator rendering: + - `ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenProjectService.java` + - `ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenProjectServiceImpl.java` + Add overloads that accept a transient `GenProject` with draft tables. + +### Admin Controllers + +- Create `ruoyi-admin/src/main/java/com/ruoyi/web/controller/front/FrontAuthController.java`. +- Create `ruoyi-admin/src/main/java/com/ruoyi/web/controller/front/FrontProjectController.java`. +- Create `ruoyi-admin/src/main/java/com/ruoyi/web/service/front/FrontAuthService.java`. + +### Front App + +- Create project root `easycode-web/`. +- Create `easycode-web/package.json`, `vite.config.js`, `index.html`, `.env.development`, `.env.production`. +- Create app files: + - `easycode-web/src/main.js` + - `easycode-web/src/App.vue` + - `easycode-web/src/router/index.js` + - `easycode-web/src/api/request.js` + - `easycode-web/src/api/auth.js` + - `easycode-web/src/api/project.js` + - `easycode-web/src/utils/auth.js` + - `easycode-web/src/styles/global.scss` + - `easycode-web/src/components/AppHeader.vue` + - `easycode-web/src/components/DatabaseDesigner.vue` + - `easycode-web/src/components/ProjectStructureTree.vue` + - `easycode-web/src/components/CodePreview.vue` + - `easycode-web/src/views/HomeView.vue` + - `easycode-web/src/views/LoginView.vue` + - `easycode-web/src/views/RegisterView.vue` + - `easycode-web/src/views/GenerateView.vue` + - `easycode-web/src/views/PreviewView.vue` + +--- + +## Task 1: Add Front Workbench Database Tables + +**Files:** +- Create: `sql/front_workbench.sql` +- Modify: `sql/db.sql` + +- [ ] **Step 1: Write the DDL script** + +Create `sql/front_workbench.sql` with this schema. Use `utf8mb4`, `innodb`, and `front_` prefixes. + +```sql +-- ================================================= +-- EasyCode front workbench tables +-- Import after sql/db.sql, or use the copy appended to sql/db.sql +-- ================================================= + +drop table if exists front_project_file; +drop table if exists front_project_generation; +drop table if exists front_project_column; +drop table if exists front_project_table; +drop table if exists front_project; +drop table if exists front_user; + +create table front_user ( + user_id bigint(20) not null auto_increment comment '前台用户ID', + username varchar(50) not null comment '用户名', + password varchar(100) not null comment '密码', + nickname varchar(50) default '' comment '昵称', + email varchar(100) default '' comment '邮箱', + phone varchar(20) default '' comment '手机号', + status char(1) default '0' comment '状态(0正常 1停用)', + del_flag char(1) default '0' comment '删除标志(0存在 2删除)', + last_login_time datetime default null comment '最后登录时间', + create_time datetime default null comment '创建时间', + update_time datetime default null comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (user_id), + unique key uk_front_user_username (username), + key idx_front_user_email (email), + key idx_front_user_phone (phone) +) engine=innodb auto_increment=100 comment='前台用户表'; + +create table front_project ( + project_id bigint(20) not null auto_increment comment '前台项目ID', + user_id bigint(20) not null comment '前台用户ID', + project_name varchar(100) not null comment '项目名称', + project_file_name varchar(100) default '' comment '项目文件名', + package_name varchar(100) default 'com.easycode.generated' comment '包名', + version varchar(20) default '1.0.0' comment '版本号', + project_desc varchar(1000) default '' comment '项目描述', + industry_template varchar(50) default '' comment '行业模板', + front_framework varchar(50) default 'Vue3 + Element Plus' comment '前台框架', + back_framework varchar(50) default 'Spring Boot + MyBatis' comment '后端框架', + generate_status char(1) default '0' comment '数据库生成状态(0未生成 1生成中 2已生成 3失败)', + preview_status char(1) default '0' comment '预览状态(0未预览 1已预览 2失败)', + status char(1) default '0' comment '状态(0正常 1停用)', + create_time datetime default null comment '创建时间', + update_time datetime default null comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (project_id), + key idx_front_project_user_id (user_id), + constraint fk_front_project_user foreign key (user_id) references front_user(user_id) +) engine=innodb auto_increment=100 comment='前台项目草稿表'; + +create table front_project_table ( + table_id bigint(20) not null auto_increment comment '表设计ID', + project_id bigint(20) not null comment '项目ID', + user_id bigint(20) not null comment '前台用户ID', + table_name varchar(200) not null comment '表名称', + table_comment varchar(500) default '' comment '表描述', + class_name varchar(100) default '' comment '实体类名称', + module_name varchar(30) default '' comment '生成模块名', + business_name varchar(30) default '' comment '生成业务名', + function_name varchar(50) default '' comment '生成功能名', + tpl_category varchar(200) default 'crud' comment '模板类型', + tpl_web_type varchar(30) default 'element-plus' comment '前端模板类型', + options varchar(1000) default '' comment '生成选项', + create_table_sql longtext comment '建表SQL', + sort int default 0 comment '排序', + status char(1) default '0' comment '状态(0正常 1停用)', + create_time datetime default null comment '创建时间', + update_time datetime default null comment '更新时间', + primary key (table_id), + unique key uk_front_project_table (project_id, table_name), + key idx_front_project_table_project_id (project_id), + constraint fk_front_project_table_project foreign key (project_id) references front_project(project_id) +) engine=innodb auto_increment=100 comment='前台项目表设计'; + +create table front_project_column ( + column_id bigint(20) not null auto_increment comment '字段设计ID', + table_id bigint(20) not null comment '表设计ID', + project_id bigint(20) not null comment '项目ID', + column_name varchar(200) not null comment '字段名称', + column_comment varchar(500) default '' comment '字段描述', + column_type varchar(100) not null comment '字段类型', + java_type varchar(500) default 'String' comment 'Java类型', + java_field varchar(200) default '' comment 'Java字段名', + is_pk char(1) default '0' comment '是否主键(1是)', + is_increment char(1) default '0' comment '是否自增(1是)', + is_required char(1) default '0' comment '是否必填(1是)', + is_insert char(1) default '1' comment '是否插入字段(1是)', + is_edit char(1) default '1' comment '是否编辑字段(1是)', + is_list char(1) default '1' comment '是否列表字段(1是)', + is_query char(1) default '0' comment '是否查询字段(1是)', + query_type varchar(200) default 'EQ' comment '查询方式', + html_type varchar(200) default 'input' comment '显示类型', + dict_type varchar(200) default '' comment '字典类型', + default_value varchar(200) default null comment '默认值', + sort int default 0 comment '排序', + create_time datetime default null comment '创建时间', + update_time datetime default null comment '更新时间', + primary key (column_id), + key idx_front_project_column_table_id (table_id), + key idx_front_project_column_project_id (project_id), + constraint fk_front_project_column_table foreign key (table_id) references front_project_table(table_id) +) engine=innodb auto_increment=100 comment='前台项目字段设计'; + +create table front_project_generation ( + generation_id bigint(20) not null auto_increment comment '生成记录ID', + project_id bigint(20) not null comment '项目ID', + user_id bigint(20) not null comment '前台用户ID', + generate_type varchar(50) not null comment '生成类型', + provider varchar(50) default 'deepseek' comment 'AI提供方', + prompt_summary varchar(500) default '' comment 'Prompt摘要', + request_payload longtext comment '请求内容', + response_payload longtext comment '响应内容', + success char(1) default '0' comment '是否成功(1成功 0失败)', + error_message varchar(1000) default '' comment '错误信息', + token_usage int default 0 comment 'Token用量', + elapsed_ms bigint(20) default 0 comment '耗时毫秒', + create_time datetime default null comment '创建时间', + primary key (generation_id), + key idx_front_project_generation_project_id (project_id) +) engine=innodb auto_increment=100 comment='前台项目生成记录'; + +create table front_project_file ( + file_id bigint(20) not null auto_increment comment '文件缓存ID', + project_id bigint(20) not null comment '项目ID', + template_type varchar(50) not null comment '模板类型', + node_id varchar(100) default '' comment '结构节点ID', + table_id bigint(20) default null comment '表设计ID', + file_name varchar(255) not null comment '文件名', + file_path varchar(500) default '' comment '文件路径', + file_content longtext comment '文件内容', + content_hash varchar(64) default '' comment '内容哈希', + create_time datetime default null comment '创建时间', + update_time datetime default null comment '更新时间', + primary key (file_id), + key idx_front_project_file_project_id (project_id), + key idx_front_project_file_lookup (project_id, template_type, table_id) +) engine=innodb auto_increment=100 comment='前台项目预览文件缓存'; +``` + +- [ ] **Step 2: Append the same DDL to `sql/db.sql`** + +Place it after the existing generator extension tables and before `SET FOREIGN_KEY_CHECKS = 1;`. Keep `drop table if exists` order from child tables to parent tables. + +- [ ] **Step 3: Run SQL static checks** + +Run: + +```powershell +git diff --check -- sql/db.sql sql/front_workbench.sql +rg -n "create table front_user|create table front_project|front_project_generation" sql/db.sql sql/front_workbench.sql +``` + +Expected: + +```text +git diff --check has no output +rg prints all six new front_* table declarations +``` + +- [ ] **Step 4: Commit** + +```powershell +git add sql/db.sql sql/front_workbench.sql +git commit -m "Add front workbench database schema" +``` + +--- + +## Task 2: Add Front Token Isolation + +**Files:** +- Modify: `ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java` +- Modify: `ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java` +- Modify: `ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java` +- Modify: `ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java` +- Modify: `ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java` + +- [ ] **Step 1: Add login type constants** + +In `Constants.java`, add: + +```java +/** 后台管理端登录类型 */ +public static final String LOGIN_TYPE_ADMIN = "admin"; + +/** 前台用户登录类型 */ +public static final String LOGIN_TYPE_FRONT = "front"; +``` + +- [ ] **Step 2: Add `loginType` to `LoginUser`** + +Add field, getter, and setter: + +```java +/** 登录类型:admin 后台用户,front 前台用户 */ +private String loginType; + +public String getLoginType() +{ + return loginType; +} + +public void setLoginType(String loginType) +{ + this.loginType = loginType; +} +``` + +Keep existing constructors unchanged. Existing tokens with `loginType == null` must be treated as admin-compatible. + +- [ ] **Step 3: Mark existing admin logins** + +In `SysLoginService.login`, immediately after: + +```java +LoginUser loginUser = (LoginUser) authentication.getPrincipal(); +``` + +add: + +```java +loginUser.setLoginType(Constants.LOGIN_TYPE_ADMIN); +``` + +- [ ] **Step 4: Gate front/admin tokens in JWT filter** + +In `JwtAuthenticationTokenFilter`, add helper methods: + +```java +private boolean isFrontRequest(HttpServletRequest request) +{ + return request.getRequestURI() != null && request.getRequestURI().startsWith("/front/"); +} + +private boolean isTokenAllowedForRequest(LoginUser loginUser, HttpServletRequest request) +{ + String loginType = loginUser.getLoginType(); + boolean frontRequest = isFrontRequest(request); + if (frontRequest) + { + return com.ruoyi.common.constant.Constants.LOGIN_TYPE_FRONT.equals(loginType); + } + return loginType == null || com.ruoyi.common.constant.Constants.LOGIN_TYPE_ADMIN.equals(loginType); +} +``` + +Then change the existing authentication block to: + +```java +if (StringUtils.isNotNull(loginUser) + && isTokenAllowedForRequest(loginUser, request) + && StringUtils.isNull(SecurityUtils.getAuthentication())) +{ + tokenService.verifyToken(loginUser); + UsernamePasswordAuthenticationToken authenticationToken = + new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authenticationToken); +} +``` + +- [ ] **Step 5: Permit front login/register anonymously** + +In `SecurityConfig.filterChain`, extend the anonymous matcher: + +```java +requests.antMatchers("/login", "/register", "/captchaImage", + "/front/auth/login", "/front/auth/register").permitAll() +``` + +Do not permit all `/front/**`. + +- [ ] **Step 6: Run backend compile** + +Run: + +```powershell +mvn -pl ruoyi-admin -am -DskipTests package +``` + +Expected: + +```text +[INFO] BUILD SUCCESS +``` + +- [ ] **Step 7: Commit** + +```powershell +git add ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java ` + ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java ` + ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java ` + ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java ` + ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java +git commit -m "Separate front and admin login tokens" +``` + +--- + +## Task 3: Add Front Domain, Mappers, and Basic Services + +**Files:** +- Create: front domain classes, mapper interfaces, mapper XML, and basic service interfaces/impls listed in File Map. +- Test: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontProjectConverterTest.java` + +- [ ] **Step 1: Create front domain classes** + +Each domain class extends `BaseEntity` where timestamps and remark are needed. Use JavaBean getters/setters, not Lombok. + +`FrontUser` fields: + +```java +private Long userId; +private String username; +private String password; +private String nickname; +private String email; +private String phone; +private String status; +private String delFlag; +private Date lastLoginTime; +``` + +`FrontProject` fields: + +```java +private Long projectId; +private Long userId; +private String projectName; +private String projectFileName; +private String packageName; +private String version; +private String projectDesc; +private String industryTemplate; +private String frontFramework; +private String backFramework; +private String generateStatus; +private String previewStatus; +private String status; +private List tables; +``` + +`FrontProjectTable` fields: + +```java +private Long tableId; +private Long projectId; +private Long userId; +private String tableName; +private String tableComment; +private String className; +private String moduleName; +private String businessName; +private String functionName; +private String tplCategory; +private String tplWebType; +private String options; +private String createTableSql; +private Integer sort; +private String status; +private List columns; +``` + +`FrontProjectColumn` fields: + +```java +private Long columnId; +private Long tableId; +private Long projectId; +private String columnName; +private String columnComment; +private String columnType; +private String javaType; +private String javaField; +private String isPk; +private String isIncrement; +private String isRequired; +private String isInsert; +private String isEdit; +private String isList; +private String isQuery; +private String queryType; +private String htmlType; +private String dictType; +private String defaultValue; +private Integer sort; +``` + +`FrontProjectGeneration` and `FrontProjectFile` mirror the SQL columns. + +- [ ] **Step 2: Create mapper interfaces** + +Use this shape for every mapper: + +```java +public interface FrontProjectMapper +{ + FrontProject selectFrontProjectById(Long projectId); + + List selectFrontProjectList(FrontProject frontProject); + + int insertFrontProject(FrontProject frontProject); + + int updateFrontProject(FrontProject frontProject); + + int deleteFrontProjectById(Long projectId); +} +``` + +Add ownership-specific methods where needed: + +```java +FrontProject selectFrontProjectByUserAndId(@Param("userId") Long userId, @Param("projectId") Long projectId); +int deleteFrontProjectByUserAndId(@Param("userId") Long userId, @Param("projectId") Long projectId); +``` + +For table and column mappers add: + +```java +List selectTablesByProjectId(Long projectId); +int deleteTablesByProjectId(Long projectId); +List selectColumnsByTableId(Long tableId); +int deleteColumnsByProjectId(Long projectId); +``` + +- [ ] **Step 3: Create mapper XML** + +Follow the existing XML style in `mapper/generator/*.xml`. Every XML must contain: + +```xml + + + + + + + + + + + + + + + + + + +``` + +Use `useGeneratedKeys="true"` and `keyProperty` for inserts. + +- [ ] **Step 4: Create basic services** + +`IFrontProjectService` must expose: + +```java +FrontProject createProject(Long userId, FrontProjectCreateRequest request); +List listProjects(Long userId); +FrontProject getProject(Long userId, Long projectId); +int updateProject(Long userId, Long projectId, FrontProjectUpdateRequest request); +int deleteProject(Long userId, Long projectId); +DatabaseDesignResponse getDatabase(Long userId, Long projectId); +DatabaseDesignResponse saveDatabase(Long userId, Long projectId, DatabaseDesignResponse request); +``` + +`saveDatabase` must delete old `front_project_column`, then old `front_project_table`, then insert the submitted table/column tree in one transaction. + +- [ ] **Step 5: Write converter test first** + +Create `FrontProjectConverterTest` with this test: + +```java +@Test +public void convertsFrontDraftToGenProject() +{ + FrontProject project = new FrontProject(); + project.setProjectId(10L); + project.setProjectName("车辆管理系统"); + project.setProjectFileName("vehicle-system"); + project.setPackageName("com.easycode.vehicle"); + project.setVersion("1.0.0"); + project.setFrontFramework("Vue3 + Element Plus"); + project.setBackFramework("Spring Boot + MyBatis"); + + FrontProjectTable table = new FrontProjectTable(); + table.setTableId(20L); + table.setTableName("car_info"); + table.setTableComment("车辆信息表"); + table.setClassName("CarInfo"); + table.setModuleName("vehicle"); + table.setBusinessName("car"); + table.setFunctionName("车辆信息"); + + FrontProjectColumn column = new FrontProjectColumn(); + column.setColumnId(30L); + column.setColumnName("car_id"); + column.setColumnComment("车辆ID"); + column.setColumnType("bigint(20)"); + column.setJavaType("Long"); + column.setJavaField("carId"); + column.setIsPk("1"); + column.setIsIncrement("1"); + table.setColumns(Collections.singletonList(column)); + project.setTables(Collections.singletonList(table)); + + GenProject genProject = FrontProjectConverter.toGenProject(project); + + assertEquals("车辆管理系统", genProject.getProjectName()); + assertEquals("com.easycode.vehicle", genProject.getPackageName()); + assertEquals(1, genProject.getTables().size()); + assertEquals("car_info", genProject.getTables().get(0).getTableName()); + assertEquals("carId", genProject.getTables().get(0).getColumns().get(0).getJavaField()); +} +``` + +- [ ] **Step 6: Run test and verify failure** + +Run: + +```powershell +mvn -pl ruoyi-generator -am -Dtest=FrontProjectConverterTest test +``` + +Expected before converter implementation: + +```text +Compilation failure: cannot find symbol FrontProjectConverter +``` + +- [ ] **Step 7: Implement `FrontProjectConverter`** + +Implement exact conversion rules: + +- `FrontProject.projectId` maps to `GenProject.projectId`. +- `FrontProject.projectName` maps to `GenProject.projectName`. +- `FrontProject.projectFileName` maps to `GenProject.projectFileName`. +- `FrontProject.packageName` maps to `GenProject.packageName`. +- `FrontProject.version` maps to `GenProject.version`. +- `FrontProject.frontFramework` maps to `GenProject.frontFramework`. +- `FrontProject.backFramework` maps to `GenProject.backFramework`. +- Each `FrontProjectTable` maps to `GenTable`. +- Each `FrontProjectColumn` maps to `GenTableColumn`. + +- [ ] **Step 8: Run tests** + +Run: + +```powershell +mvn -pl ruoyi-generator -am -Dtest=FrontProjectConverterTest test +``` + +Expected: + +```text +Tests run: 1, Failures: 0, Errors: 0 +``` + +- [ ] **Step 9: Commit** + +```powershell +git add ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front ` + ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/front ` + ruoyi-generator/src/main/resources/mapper/front ` + ruoyi-generator/src/main/java/com/ruoyi/generator/service/front ` + ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontProjectConverterTest.java +git commit -m "Add front project draft model" +``` + +--- + +## Task 4: Add Front Authentication APIs + +**Files:** +- Create: `ruoyi-admin/src/main/java/com/ruoyi/web/service/front/FrontAuthService.java` +- Create: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/front/FrontAuthController.java` +- Create DTOs if not already created: + - `FrontLoginBody.java` + - `FrontRegisterBody.java` +- Modify: `IFrontUserService.java`, `FrontUserServiceImpl.java` + +- [ ] **Step 1: Add front user service methods** + +`IFrontUserService` must include: + +```java +FrontUser selectByUsername(String username); +FrontUser selectById(Long userId); +int register(FrontRegisterBody body); +int updateLastLoginTime(Long userId); +``` + +Registration rules: + +- username required, length 2 to 50 +- password required, length 5 to 50 +- duplicate username raises `ServiceException("前台账号已存在")` +- password stored with `SecurityUtils.encryptPassword` +- default nickname is username +- default status is `"0"` +- default delFlag is `"0"` + +- [ ] **Step 2: Implement front auth service** + +`FrontAuthService.login` must: + +1. Load front user by username. +2. Reject missing user, disabled user, deleted user, and wrong password. +3. Build a `SysUser` object only for token compatibility. +4. Build a `LoginUser`. +5. Set `loginType` to `Constants.LOGIN_TYPE_FRONT`. +6. Set permissions to `Collections.emptySet()`. +7. Call `tokenService.createToken(loginUser)`. +8. Update `last_login_time`. + +Core snippet: + +```java +SysUser tokenUser = new SysUser(); +tokenUser.setUserId(frontUser.getUserId()); +tokenUser.setUserName(frontUser.getUsername()); +tokenUser.setNickName(frontUser.getNickname()); +tokenUser.setPassword(frontUser.getPassword()); + +LoginUser loginUser = new LoginUser(frontUser.getUserId(), null, tokenUser, Collections.emptySet()); +loginUser.setLoginType(Constants.LOGIN_TYPE_FRONT); +String token = tokenService.createToken(loginUser); +frontUserService.updateLastLoginTime(frontUser.getUserId()); +return token; +``` + +- [ ] **Step 3: Implement controller** + +`FrontAuthController` endpoints: + +```java +@RestController +@RequestMapping("/front/auth") +public class FrontAuthController extends BaseController +{ + @PostMapping("/register") + public AjaxResult register(@RequestBody FrontRegisterBody body) + + @PostMapping("/login") + public AjaxResult login(@RequestBody FrontLoginBody body) + + @GetMapping("/profile") + public AjaxResult profile() + + @PostMapping("/logout") + public AjaxResult logout() +} +``` + +`login` returns: + +```java +AjaxResult ajax = AjaxResult.success(); +ajax.put(Constants.TOKEN, token); +return ajax; +``` + +`profile` returns the front user from `FrontUserService.selectById(SecurityUtils.getUserId())`. + +- [ ] **Step 4: Add service test** + +Create `ruoyi-admin/src/test/java/com/ruoyi/web/service/front/FrontAuthServiceTest.java`. Add test dependencies to `ruoyi-admin/pom.xml` if missing: + +```xml + + junit + junit + test + + + org.mockito + mockito-core + test + +``` + +Test: + +```java +@Test +public void loginCreatesFrontToken() +{ + FrontUser user = new FrontUser(); + user.setUserId(100L); + user.setUsername("demo"); + user.setNickname("Demo"); + user.setPassword(SecurityUtils.encryptPassword("admin123")); + user.setStatus("0"); + user.setDelFlag("0"); + + when(frontUserService.selectByUsername("demo")).thenReturn(user); + when(tokenService.createToken(any(LoginUser.class))).thenAnswer(invocation -> { + LoginUser loginUser = invocation.getArgument(0); + assertEquals(Constants.LOGIN_TYPE_FRONT, loginUser.getLoginType()); + assertEquals(Long.valueOf(100L), loginUser.getUserId()); + return "front-token"; + }); + + String token = frontAuthService.login("demo", "admin123"); + + assertEquals("front-token", token); + verify(frontUserService).updateLastLoginTime(100L); +} +``` + +- [ ] **Step 5: Run tests** + +Run: + +```powershell +mvn -pl ruoyi-admin -am -Dtest=FrontAuthServiceTest test +``` + +Expected: + +```text +Tests run: 1, Failures: 0, Errors: 0 +``` + +- [ ] **Step 6: Commit** + +```powershell +git add ruoyi-admin/src/main/java/com/ruoyi/web/controller/front/FrontAuthController.java ` + ruoyi-admin/src/main/java/com/ruoyi/web/service/front/FrontAuthService.java ` + ruoyi-admin/src/test/java/com/ruoyi/web/service/front/FrontAuthServiceTest.java ` + ruoyi-admin/pom.xml ` + ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/FrontLoginBody.java ` + ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/FrontRegisterBody.java ` + ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/IFrontUserService.java ` + ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontUserServiceImpl.java +git commit -m "Add front user authentication" +``` + +--- + +## Task 5: Add Project Draft APIs and Editable Database Save + +**Files:** +- Create: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/front/FrontProjectController.java` +- Modify: `IFrontProjectService.java` +- Modify: `FrontProjectServiceImpl.java` +- Create/modify DTO classes for project create/update/database response. + +- [ ] **Step 1: Implement project CRUD controller** + +Controller methods: + +```java +@RestController +@RequestMapping("/front/project") +public class FrontProjectController extends BaseController +{ + @Autowired + private IFrontProjectService frontProjectService; + + @PostMapping("/create") + public AjaxResult create(@RequestBody FrontProjectCreateRequest request) + + @GetMapping("/list") + public AjaxResult list() + + @GetMapping("/{projectId}") + public AjaxResult get(@PathVariable Long projectId) + + @PutMapping("/{projectId}") + public AjaxResult update(@PathVariable Long projectId, @RequestBody FrontProjectUpdateRequest request) + + @DeleteMapping("/{projectId}") + public AjaxResult delete(@PathVariable Long projectId) + + @GetMapping("/{projectId}/database") + public AjaxResult database(@PathVariable Long projectId) + + @PutMapping("/{projectId}/database") + public AjaxResult saveDatabase(@PathVariable Long projectId, @RequestBody DatabaseDesignResponse request) +} +``` + +Every method gets `Long userId = SecurityUtils.getUserId()` and only touches rows owned by that `userId`. + +- [ ] **Step 2: Implement project create defaults** + +`FrontProjectServiceImpl.createProject` must default: + +```java +projectFileName = lower hyphen form of projectName, e.g. "车辆管理系统" -> "easycode-project" +packageName = request.packageName if present else "com.easycode.generated" +version = request.version if present else "1.0.0" +frontFramework = "Vue3 + Element Plus" +backFramework = "Spring Boot + MyBatis" +generateStatus = "0" +previewStatus = "0" +status = "0" +``` + +When Chinese project names cannot be slugified safely, use `"easycode-project-" + System.currentTimeMillis()`. + +- [ ] **Step 3: Implement `getDatabase`** + +Return: + +```java +DatabaseDesignResponse response = new DatabaseDesignResponse(); +response.setProjectId(projectId); +response.setTables(tableDesigns); +response.setSql(joinedCreateTableSql); +``` + +Join SQL with two line breaks between tables. + +- [ ] **Step 4: Implement `saveDatabase` validation** + +Before saving: + +- project must belong to user +- at least one table +- every table name matches `^[a-z][a-z0-9_]{1,63}$` +- every table has at least one column +- every column name matches `^[a-z][a-z0-9_]{1,63}$` +- every column type passes `AiDatabaseSchemaValidator.isAllowedColumnType` +- exactly one primary key per table + +Raise `ServiceException` with messages: + +```text +项目不存在或无权限 +至少需要一张数据表 +表名只能包含小写字母、数字和下划线 +每张表至少需要一个字段 +字段名只能包含小写字母、数字和下划线 +字段类型不在允许范围内 +每张表必须且只能有一个主键 +``` + +- [ ] **Step 5: Run generator tests** + +Run: + +```powershell +mvn -pl ruoyi-generator -am -DfailIfNoTests=false test +``` + +Expected: + +```text +[INFO] BUILD SUCCESS +``` + +- [ ] **Step 6: Commit** + +```powershell +git add ruoyi-admin/src/main/java/com/ruoyi/web/controller/front/FrontProjectController.java ` + ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto ` + ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/IFrontProjectService.java ` + ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontProjectServiceImpl.java +git commit -m "Add front project draft APIs" +``` + +--- + +## Task 6: Add DeepSeek-backed AI Database Generation + +**Files:** +- Create: `ruoyi-generator/src/main/java/com/ruoyi/generator/config/DeepSeekProperties.java` +- Create/modify: `IAiGenerateService.java`, `AiGenerateServiceImpl.java`, `IDeepSeekClient.java`, `HttpDeepSeekClient.java`, `AiDatabaseSchemaValidator.java`, `MysqlDdlBuilder.java` +- Modify: `FrontProjectController.java` +- Modify: `application.yml` +- Test: `AiDatabaseSchemaValidatorTest.java`, `MysqlDdlBuilderTest.java`, `AiGenerateServiceImplTest.java` + +- [ ] **Step 1: Add DeepSeek config properties** + +Create: + +```java +@Component +@ConfigurationProperties(prefix = "deepseek") +public class DeepSeekProperties +{ + private String apiKey; + private String baseUrl = "https://api.deepseek.com"; + private String model = "deepseek-chat"; + private Integer timeout = 60000; + private Integer maxTables = 12; + private Integer maxColumnsPerTable = 30; + + // getters and setters +} +``` + +Add to `application.yml`: + +```yaml +deepseek: + apiKey: ${DEEPSEEK_API_KEY:} + baseUrl: https://api.deepseek.com + model: deepseek-chat + timeout: 60000 + maxTables: 12 + maxColumnsPerTable: 30 +``` + +- [ ] **Step 2: Implement DeepSeek client** + +`IDeepSeekClient`: + +```java +public interface IDeepSeekClient +{ + String chat(String prompt); +} +``` + +`HttpDeepSeekClient` uses `RestTemplate`. Request: + +```json +{ + "model": "deepseek-chat", + "messages": [ + {"role": "system", "content": "你是企业管理系统数据库设计助手,只返回JSON。"}, + {"role": "user", "content": ""} + ], + "temperature": 0.2 +} +``` + +Response parser returns `choices[0].message.content`. If `apiKey` is blank, throw: + +```java +throw new ServiceException("DeepSeek API Key 未配置"); +``` + +- [ ] **Step 3: Implement validator** + +Allowed type patterns: + +```java +private static final Pattern NAME = Pattern.compile("^[a-z][a-z0-9_]{1,63}$"); +private static final Pattern BIGINT = Pattern.compile("^bigint(\\(20\\))?$"); +private static final Pattern INT = Pattern.compile("^int(\\(11\\))?$"); +private static final Pattern VARCHAR = Pattern.compile("^varchar\\(([1-9][0-9]{0,2}|1000)\\)$"); +private static final Pattern CHAR = Pattern.compile("^char\\(([1-9][0-9]?)\\)$"); +private static final Pattern DECIMAL = Pattern.compile("^decimal\\(([1-9][0-9]?),([1-9][0-9]?)\\)$"); +``` + +Also allow exact: + +```text +datetime, date, text, longtext +``` + +Reject SQL containing case-insensitive: + +```text +drop, truncate, alter user, grant, revoke, load_file, into outfile +``` + +- [ ] **Step 4: Implement DDL builder** + +For each table produce: + +```sql +CREATE TABLE `car_info` ( + `car_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '车辆ID', + `car_no` varchar(30) NOT NULL COMMENT '车牌号', + PRIMARY KEY (`car_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆信息表'; +``` + +Rules: + +- primary key with `isIncrement == "1"` gets `AUTO_INCREMENT` +- required field gets `NOT NULL` +- optional field gets `DEFAULT NULL` +- string comments escape single quote as doubled single quote +- one DDL string per table saved to `FrontProjectTable.createTableSql` + +- [ ] **Step 5: Write validator tests** + +Test cases: + +```java +assertTrue(validator.isValidName("car_info")); +assertFalse(validator.isValidName("CarInfo")); +assertFalse(validator.isValidName("sys user")); +assertTrue(validator.isAllowedColumnType("varchar(100)")); +assertTrue(validator.isAllowedColumnType("decimal(10,2)")); +assertFalse(validator.isAllowedColumnType("json")); +assertFalse(validator.isSafeSql("drop table sys_user")); +``` + +- [ ] **Step 6: Write AI service test with mocked client** + +Mock response: + +```json +{ + "tables": [ + { + "tableName": "car_info", + "tableComment": "车辆信息表", + "columns": [ + { + "columnName": "car_id", + "columnType": "bigint(20)", + "javaType": "Long", + "javaField": "carId", + "isPk": "1", + "isIncrement": "1", + "isRequired": "1", + "columnComment": "车辆ID" + }, + { + "columnName": "car_no", + "columnType": "varchar(30)", + "javaType": "String", + "javaField": "carNo", + "isPk": "0", + "isIncrement": "0", + "isRequired": "1", + "columnComment": "车牌号" + } + ] + } + ] +} +``` + +Expected: + +- inserts one `front_project_table` +- inserts two `front_project_column` +- saves one generation record with `success = "1"` +- returns SQL containing `CREATE TABLE \`car_info\`` + +- [ ] **Step 7: Add controller endpoint** + +In `FrontProjectController`: + +```java +@PostMapping("/{projectId}/generate-database") +public AjaxResult generateDatabase(@PathVariable Long projectId, @RequestBody GenerateDatabaseRequest request) +{ + Long userId = SecurityUtils.getUserId(); + return success(aiGenerateService.generateDatabase(userId, projectId, request)); +} +``` + +- [ ] **Step 8: Run tests** + +Run: + +```powershell +mvn -pl ruoyi-generator -am -Dtest=AiDatabaseSchemaValidatorTest,MysqlDdlBuilderTest,AiGenerateServiceImplTest test +``` + +Expected: + +```text +Tests run: at least 3, Failures: 0, Errors: 0 +``` + +- [ ] **Step 9: Commit** + +```powershell +git add ruoyi-admin/src/main/resources/application.yml ` + ruoyi-admin/src/main/java/com/ruoyi/web/controller/front/FrontProjectController.java ` + ruoyi-generator/src/main/java/com/ruoyi/generator/config/DeepSeekProperties.java ` + ruoyi-generator/src/main/java/com/ruoyi/generator/service/front ` + ruoyi-generator/src/test/java/com/ruoyi/generator/service/front +git commit -m "Add DeepSeek database generation service" +``` + +--- + +## Task 7: Adapt Generator Preview for Front Drafts + +**Files:** +- Modify: `IGenProjectService.java` +- Modify: `GenProjectServiceImpl.java` +- Create/modify: `IFrontProjectPreviewService.java`, `FrontProjectPreviewServiceImpl.java` +- Modify: `FrontProjectController.java` +- Test: `FrontProjectPreviewServiceImplTest.java` + +- [ ] **Step 1: Add generator overloads** + +In `IGenProjectService`, add: + +```java +List> getProjectStructure(GenProject project, String type); + +Map previewCode(GenProject project, Long tableId, String keyword, String type); + +byte[] downloadStructure(GenProject project, String type); +``` + +Existing ID-based methods stay unchanged and delegate to the new object-based overload after loading `GenProject`. + +- [ ] **Step 2: Refactor `GenProjectServiceImpl`** + +The object overload must not query `gen_project` by ID. It may still query active templates and project structures. + +Template resolution rules: + +1. If a project has a `sys_project_template` row for `project.projectId` and `type`, use it. +2. Otherwise choose the first active `sys_template` where `template_type = type`. +3. If none exists, throw `ServiceException("未配置可用模板:" + type)`. + +Structure query rules: + +- For admin projects, keep existing project template structure lookup. +- For front draft projects without `sys_project_template`, use `sys_project_structure` nodes for the resolved template ID. +- Expand table-scoped nodes for every `GenTable` in `project.getTables()`. + +- [ ] **Step 3: Implement front preview service** + +`IFrontProjectPreviewService`: + +```java +List> getStructure(Long userId, Long projectId, String templateType); +Map getFileContent(Long userId, Long projectId, PreviewFileRequest request); +byte[] download(Long userId, Long projectId, String templateType); +``` + +Implementation: + +1. Load `FrontProject` with tables and columns by user/project ID. +2. Convert to `GenProject`. +3. Call `genProjectService.getProjectStructure(genProject, templateType)`. +4. Call `genProjectService.previewCode(genProject, tableId, category, templateType)` for file content. +5. Call `genProjectService.downloadStructure(genProject, templateType)` for zip. + +- [ ] **Step 4: Add preview endpoints** + +In `FrontProjectController`: + +```java +@PostMapping("/{projectId}/preview") +public AjaxResult preview(@PathVariable Long projectId) + +@GetMapping("/{projectId}/structure") +public AjaxResult structure(@PathVariable Long projectId, @RequestParam String templateType) + +@GetMapping("/{projectId}/file-content") +public AjaxResult fileContent(@PathVariable Long projectId, PreviewFileRequest request) + +@GetMapping("/{projectId}/download") +public void download(@PathVariable Long projectId, @RequestParam String templateType, HttpServletResponse response) +``` + +`preview` marks `front_project.preview_status = "1"` after structure can be generated for `backend`, `frontend`, and `admin_frontend`. + +- [ ] **Step 5: Write preview service test** + +Test: + +```java +@Test +public void returnsStructureForDraftProject() +{ + when(frontProjectService.getProject(100L, 200L)).thenReturn(frontProjectWithOneTable()); + when(genProjectService.getProjectStructure(any(GenProject.class), eq("backend"))) + .thenReturn(Collections.singletonList(Collections.singletonMap("label", "backend"))); + + List> structure = previewService.getStructure(100L, 200L, "backend"); + + assertEquals("backend", structure.get(0).get("label")); +} +``` + +- [ ] **Step 6: Run tests** + +Run: + +```powershell +mvn -pl ruoyi-generator -am -Dtest=FrontProjectPreviewServiceImplTest,GenProjectServiceImplTest test +``` + +Expected: + +```text +Tests run: existing generator tests plus preview test, Failures: 0, Errors: 0 +``` + +- [ ] **Step 7: Commit** + +```powershell +git add ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenProjectService.java ` + ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenProjectServiceImpl.java ` + ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/IFrontProjectPreviewService.java ` + ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontProjectPreviewServiceImpl.java ` + ruoyi-admin/src/main/java/com/ruoyi/web/controller/front/FrontProjectController.java ` + ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontProjectPreviewServiceImplTest.java +git commit -m "Support front draft project preview" +``` + +--- + +## Task 8: Scaffold Independent `easycode-web` + +**Files:** +- Create all `easycode-web` files listed in File Map. + +- [ ] **Step 1: Create package and Vite config** + +`easycode-web/package.json`: + +```json +{ + "name": "easycode-web", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --host 0.0.0.0", + "build": "vite build", + "preview": "vite preview --host 0.0.0.0" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "axios": "^1.7.9", + "element-plus": "^2.9.1", + "vue": "^3.5.13", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.1", + "sass": "^1.83.0", + "vite": "^6.0.7" + } +} +``` + +`easycode-web/vite.config.js`: + +```js +import { defineConfig, loadEnv } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd()) + return { + plugins: [vue()], + server: { + port: 5174, + proxy: { + '/front': { + target: env.VITE_API_BASE_URL || 'http://localhost:8080', + changeOrigin: true + } + } + } + } +}) +``` + +`.env.development`: + +```env +VITE_API_BASE_URL=http://localhost:8080 +``` + +- [ ] **Step 2: Create router guard** + +`easycode-web/src/router/index.js`: + +```js +import { createRouter, createWebHistory } from 'vue-router' +import { getToken } from '@/utils/auth' + +const routes = [ + { path: '/', name: 'Home', component: () => import('@/views/HomeView.vue') }, + { path: '/login', name: 'Login', component: () => import('@/views/LoginView.vue') }, + { path: '/register', name: 'Register', component: () => import('@/views/RegisterView.vue') }, + { path: '/generate', name: 'Generate', component: () => import('@/views/GenerateView.vue'), meta: { requiresAuth: true } }, + { path: '/project/:projectId/preview', name: 'Preview', component: () => import('@/views/PreviewView.vue'), meta: { requiresAuth: true } } +] + +const router = createRouter({ + history: createWebHistory(), + routes +}) + +router.beforeEach((to) => { + if (to.meta.requiresAuth && !getToken()) { + return { path: '/login', query: { redirect: to.fullPath } } + } + return true +}) + +export default router +``` + +- [ ] **Step 3: Create request wrapper** + +`easycode-web/src/api/request.js`: + +```js +import axios from 'axios' +import { ElMessage } from 'element-plus' +import { getToken, removeToken } from '@/utils/auth' + +const service = axios.create({ + baseURL: '', + timeout: 60000 +}) + +service.interceptors.request.use((config) => { + const token = getToken() + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config +}) + +service.interceptors.response.use( + (response) => { + const data = response.data + if (data && data.code && data.code !== 200) { + ElMessage.error(data.msg || '请求失败') + return Promise.reject(new Error(data.msg || '请求失败')) + } + return data + }, + (error) => { + if (error.response && error.response.status === 401) { + removeToken() + ElMessage.error('登录已过期,请重新登录') + } else { + ElMessage.error(error.message || '网络异常') + } + return Promise.reject(error) + } +) + +export default service +``` + +- [ ] **Step 4: Create home page** + +Use the prototype as content reference but write clean Vue and Chinese text. Keep the first viewport as the usable generation entry, not a marketing-only page. + +Hero actions: + +```js +const startGenerate = () => { + router.push({ + path: '/generate', + query: { keyword: keyword.value } + }) +} +``` + +Login action: + +```js +router.push('/login') +``` + +- [ ] **Step 5: Create generate page with mock fallback** + +`GenerateView.vue` must support these states: + +```js +const activeStep = ref(0) +const generating = ref(false) +const projectId = ref(null) +const database = ref({ tables: [], sql: '' }) +``` + +Buttons: + +- “生成数据库” calls `createProject`, then `generateDatabase`. +- “保存表结构” calls `saveDatabase`. +- “预览项目” routes to `/project/${projectId}/preview`. + +- [ ] **Step 6: Create preview page** + +`PreviewView.vue` loads three structures: + +```js +const templateTypes = [ + { label: '后端项目', value: 'backend' }, + { label: '前台前端', value: 'frontend' }, + { label: '后台前端', value: 'admin_frontend' } +] +``` + +Clicking a file node calls: + +```js +getFileContent(projectId, { + templateType: activeTemplateType.value, + nodeId: node.id, + tableId: node.tableId, + category: node.category +}) +``` + +- [ ] **Step 7: Install and build** + +Run: + +```powershell +cd easycode-web +npm install +npm run build +``` + +Expected: + +```text +vite build succeeds and creates easycode-web/dist +``` + +If `npm install` fails because network access is blocked, rerun with escalation and keep the same command. + +- [ ] **Step 8: Commit** + +```powershell +git add easycode-web +git commit -m "Add EasyCode front web app" +``` + +--- + +## Task 9: Wire Front App to Real APIs + +**Files:** +- Modify: `easycode-web/src/api/auth.js` +- Modify: `easycode-web/src/api/project.js` +- Modify: `easycode-web/src/views/LoginView.vue` +- Modify: `easycode-web/src/views/RegisterView.vue` +- Modify: `easycode-web/src/views/GenerateView.vue` +- Modify: `easycode-web/src/views/PreviewView.vue` + +- [ ] **Step 1: Implement auth API** + +`auth.js`: + +```js +import request from './request' + +export function login(data) { + return request.post('/front/auth/login', data) +} + +export function register(data) { + return request.post('/front/auth/register', data) +} + +export function profile() { + return request.get('/front/auth/profile') +} +``` + +- [ ] **Step 2: Implement project API** + +`project.js`: + +```js +import request from './request' + +function unwrap(res) { + return res && Object.prototype.hasOwnProperty.call(res, 'data') ? res.data : res +} + +export function createProject(data) { + return request.post('/front/project/create', data).then(unwrap) +} + +export function generateDatabase(projectId, data) { + return request.post(`/front/project/${projectId}/generate-database`, data).then(unwrap) +} + +export function getDatabase(projectId) { + return request.get(`/front/project/${projectId}/database`).then(unwrap) +} + +export function saveDatabase(projectId, data) { + return request.put(`/front/project/${projectId}/database`, data).then(unwrap) +} + +export function getStructure(projectId, templateType) { + return request.get(`/front/project/${projectId}/structure`, { params: { templateType } }).then(unwrap) +} + +export function getFileContent(projectId, params) { + return request.get(`/front/project/${projectId}/file-content`, { params }).then(unwrap) +} +``` + +- [ ] **Step 3: Use token from login** + +On successful login: + +```js +const res = await login(form) +setToken(res.token) +router.push(route.query.redirect || '/generate') +``` + +- [ ] **Step 4: Wire generate page** + +On “生成数据库”: + +```js +const project = await createProject({ + projectName: form.projectName, + projectDesc: form.projectDesc, + industryTemplate: form.industryTemplate +}) +projectId.value = project.projectId +const result = await generateDatabase(projectId.value, form) +database.value = result +activeStep.value = 1 +``` + +The `unwrap` helper in `project.js` normalizes `AjaxResult.success(data)` to the inner `data` object. + +- [ ] **Step 5: Wire preview page** + +Load structures after route enter: + +```js +for (const item of templateTypes) { + const res = await getStructure(projectId, item.value) + structures.value[item.value] = res || [] +} +``` + +Click file node: + +```js +const res = await getFileContent(projectId, { + templateType: activeTemplateType.value, + nodeId: node.id, + tableId: node.tableId, + category: node.category +}) +code.value = res && res.content ? res.content : '' +fileName.value = node.label +``` + +- [ ] **Step 6: Build front app** + +Run: + +```powershell +cd easycode-web +npm run build +``` + +Expected: + +```text +vite build succeeds +``` + +- [ ] **Step 7: Commit** + +```powershell +git add easycode-web/src +git commit -m "Connect front web app to workbench APIs" +``` + +--- + +## Task 10: End-to-End Verification and Documentation + +**Files:** +- Modify: `README.md` +- Create or modify: `easycode-web/README.md` +- Optional create: `sql/front_workbench_test_data.sql` + +- [ ] **Step 1: Add front app README** + +`easycode-web/README.md` must include: + +```markdown +# EasyCode Web + +## Start + +```powershell +npm install +npm run dev +``` + +Default URL: http://localhost:5174 + +## Backend + +Set `VITE_API_BASE_URL` in `.env.development`. + +## Flow + +1. Register or login front user. +2. Enter project name and description. +3. Generate MySQL database design through backend DeepSeek service. +4. Edit tables and fields. +5. Preview backend, frontend, and admin frontend project files. +``` +``` + +- [ ] **Step 2: Update root README** + +Add a short “前台生成工作台” section with: + +```markdown +### 前台生成工作台 + +- 前台目录:`easycode-web` +- 后台接口前缀:`/front/**` +- DeepSeek 只在后端 `AiGenerateService` 中调用 +- 新增数据库脚本:`sql/front_workbench.sql` +``` + +- [ ] **Step 3: Backend verification** + +Run: + +```powershell +mvn -pl ruoyi-admin -am -DfailIfNoTests=false test +``` + +Expected: + +```text +[INFO] BUILD SUCCESS +``` + +- [ ] **Step 4: Frontend verification** + +Run: + +```powershell +cd easycode-web +npm run build +``` + +Expected: + +```text +vite build succeeds +``` + +- [ ] **Step 5: Manual smoke test** + +Start backend and front app. Use these checks: + +```text +1. Open http://localhost:5174 +2. Click 登录 and register a front user +3. Login redirects back to /generate +4. Create project "车辆管理系统" +5. Click 生成数据库 +6. Tables are displayed and editable +7. Save database design +8. Click 预览项目 +9. backend/frontend/admin_frontend structures load +10. Click a file and code appears on the right +``` + +- [ ] **Step 6: Commit** + +```powershell +git add README.md easycode-web/README.md +git commit -m "Document front workbench setup" +``` + +--- + +## Final Verification Before Merge + +Run from repository root: + +```powershell +git status -sb +git diff --check +mvn -pl ruoyi-admin -am -DfailIfNoTests=false test +``` + +Run from `easycode-web`: + +```powershell +npm run build +``` + +Expected: + +```text +working tree clean after final commit +git diff --check has no output +Maven BUILD SUCCESS +Vite build succeeds +``` + +## Implementation Notes + +- Keep DeepSeek API key only in backend environment/config. +- Do not add DeepSeek calls to `easycode-web`. +- Use front token only for `/front/**`. +- Do not reuse `sys_user` for front users. +- Do not save AI output directly without `AiDatabaseSchemaValidator`. +- Keep generated draft data in `front_*` tables; only convert to `GenProject` in memory for preview/download.