Files
yidaima/RuoYi-Vue/docs/superpowers/plans/2026-05-21-easycode-front-workbench.md

1844 lines
56 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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<FrontProjectTable> 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<FrontProjectColumn> 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<FrontProject> 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<FrontProjectTable> selectTablesByProjectId(Long projectId);
int deleteTablesByProjectId(Long projectId);
List<FrontProjectColumn> 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
<resultMap type="FrontProject" id="FrontProjectResult">
<id property="projectId" column="project_id" />
<result property="userId" column="user_id" />
<result property="projectName" column="project_name" />
<result property="projectFileName" column="project_file_name" />
<result property="packageName" column="package_name" />
<result property="version" column="version" />
<result property="projectDesc" column="project_desc" />
<result property="industryTemplate" column="industry_template" />
<result property="frontFramework" column="front_framework" />
<result property="backFramework" column="back_framework" />
<result property="generateStatus" column="generate_status" />
<result property="previewStatus" column="preview_status" />
<result property="status" column="status" />
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
</resultMap>
```
Use `useGeneratedKeys="true"` and `keyProperty` for inserts.
- [ ] **Step 4: Create basic services**
`IFrontProjectService` must expose:
```java
FrontProject createProject(Long userId, FrontProjectCreateRequest request);
List<FrontProject> 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
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
```
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": "<prompt>"}
],
"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<Map<String, Object>> getProjectStructure(GenProject project, String type);
Map<String, String> 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<Map<String, Object>> getStructure(Long userId, Long projectId, String templateType);
Map<String, String> 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<Map<String, Object>> 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.