56 KiB
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:
- Database/auth foundation.
- Project draft and editable database design.
- DeepSeek-backed database generation.
- Generator preview/download adaptation.
- 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: addloginType. - 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/loginand/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.javaFrontProject.javaFrontProjectTable.javaFrontProjectColumn.javaFrontProjectGeneration.javaFrontProjectFile.java
- Create DTO classes under
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/:FrontLoginBody.javaFrontRegisterBody.javaFrontProjectCreateRequest.javaFrontProjectUpdateRequest.javaGenerateDatabaseRequest.javaDatabaseDesignResponse.javaDatabaseTableDesign.javaDatabaseColumnDesign.javaPreviewFileRequest.javaProjectStructureResponse.java
- Create mappers under
ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/front/:FrontUserMapper.javaFrontProjectMapper.javaFrontProjectTableMapper.javaFrontProjectColumnMapper.javaFrontProjectGenerationMapper.javaFrontProjectFileMapper.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.javaFrontUserServiceImpl.javaIFrontProjectService.javaFrontProjectServiceImpl.javaIAiGenerateService.javaAiGenerateServiceImpl.javaIDeepSeekClient.javaHttpDeepSeekClient.javaAiDatabaseSchemaValidator.javaMysqlDdlBuilder.javaFrontProjectConverter.javaIFrontProjectPreviewService.javaFrontProjectPreviewServiceImpl.java
- Modify
ruoyi-generator/src/main/java/com/ruoyi/generator/config/:- Create
DeepSeekProperties.java.
- Create
- Modify existing generator rendering:
ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenProjectService.javaruoyi-generator/src/main/java/com/ruoyi/generator/service/GenProjectServiceImpl.javaAdd overloads that accept a transientGenProjectwith 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.jseasycode-web/src/App.vueeasycode-web/src/router/index.jseasycode-web/src/api/request.jseasycode-web/src/api/auth.jseasycode-web/src/api/project.jseasycode-web/src/utils/auth.jseasycode-web/src/styles/global.scsseasycode-web/src/components/AppHeader.vueeasycode-web/src/components/DatabaseDesigner.vueeasycode-web/src/components/ProjectStructureTree.vueeasycode-web/src/components/CodePreview.vueeasycode-web/src/views/HomeView.vueeasycode-web/src/views/LoginView.vueeasycode-web/src/views/RegisterView.vueeasycode-web/src/views/GenerateView.vueeasycode-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.
-- =================================================
-- 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:
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:
git diff --check has no output
rg prints all six new front_* table declarations
- Step 4: Commit
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:
/** 后台管理端登录类型 */
public static final String LOGIN_TYPE_ADMIN = "admin";
/** 前台用户登录类型 */
public static final String LOGIN_TYPE_FRONT = "front";
- Step 2: Add
loginTypetoLoginUser
Add field, getter, and setter:
/** 登录类型: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:
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
add:
loginUser.setLoginType(Constants.LOGIN_TYPE_ADMIN);
- Step 4: Gate front/admin tokens in JWT filter
In JwtAuthenticationTokenFilter, add helper methods:
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:
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:
requests.antMatchers("/login", "/register", "/captchaImage",
"/front/auth/login", "/front/auth/register").permitAll()
Do not permit all /front/**.
- Step 6: Run backend compile
Run:
mvn -pl ruoyi-admin -am -DskipTests package
Expected:
[INFO] BUILD SUCCESS
- Step 7: Commit
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:
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:
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:
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:
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:
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:
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:
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:
<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:
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:
@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:
mvn -pl ruoyi-generator -am -Dtest=FrontProjectConverterTest test
Expected before converter implementation:
Compilation failure: cannot find symbol FrontProjectConverter
- Step 7: Implement
FrontProjectConverter
Implement exact conversion rules:
-
FrontProject.projectIdmaps toGenProject.projectId. -
FrontProject.projectNamemaps toGenProject.projectName. -
FrontProject.projectFileNamemaps toGenProject.projectFileName. -
FrontProject.packageNamemaps toGenProject.packageName. -
FrontProject.versionmaps toGenProject.version. -
FrontProject.frontFrameworkmaps toGenProject.frontFramework. -
FrontProject.backFrameworkmaps toGenProject.backFramework. -
Each
FrontProjectTablemaps toGenTable. -
Each
FrontProjectColumnmaps toGenTableColumn. -
Step 8: Run tests
Run:
mvn -pl ruoyi-generator -am -Dtest=FrontProjectConverterTest test
Expected:
Tests run: 1, Failures: 0, Errors: 0
- Step 9: Commit
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.javaFrontRegisterBody.java
-
Modify:
IFrontUserService.java,FrontUserServiceImpl.java -
Step 1: Add front user service methods
IFrontUserService must include:
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:
- Load front user by username.
- Reject missing user, disabled user, deleted user, and wrong password.
- Build a
SysUserobject only for token compatibility. - Build a
LoginUser. - Set
loginTypetoConstants.LOGIN_TYPE_FRONT. - Set permissions to
Collections.emptySet(). - Call
tokenService.createToken(loginUser). - Update
last_login_time.
Core snippet:
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:
@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:
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:
<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:
@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:
mvn -pl ruoyi-admin -am -Dtest=FrontAuthServiceTest test
Expected:
Tests run: 1, Failures: 0, Errors: 0
- Step 6: Commit
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:
@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:
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:
DatabaseDesignResponse response = new DatabaseDesignResponse();
response.setProjectId(projectId);
response.setTables(tableDesigns);
response.setSql(joinedCreateTableSql);
Join SQL with two line breaks between tables.
- Step 4: Implement
saveDatabasevalidation
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:
项目不存在或无权限
至少需要一张数据表
表名只能包含小写字母、数字和下划线
每张表至少需要一个字段
字段名只能包含小写字母、数字和下划线
字段类型不在允许范围内
每张表必须且只能有一个主键
- Step 5: Run generator tests
Run:
mvn -pl ruoyi-generator -am -DfailIfNoTests=false test
Expected:
[INFO] BUILD SUCCESS
- Step 6: Commit
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:
@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:
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:
public interface IDeepSeekClient
{
String chat(String prompt);
}
HttpDeepSeekClient uses RestTemplate. Request:
{
"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:
throw new ServiceException("DeepSeek API Key 未配置");
- Step 3: Implement validator
Allowed type patterns:
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:
datetime, date, text, longtext
Reject SQL containing case-insensitive:
drop, truncate, alter user, grant, revoke, load_file, into outfile
- Step 4: Implement DDL builder
For each table produce:
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"getsAUTO_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:
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:
{
"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:
@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:
mvn -pl ruoyi-generator -am -Dtest=AiDatabaseSchemaValidatorTest,MysqlDdlBuilderTest,AiGenerateServiceImplTest test
Expected:
Tests run: at least 3, Failures: 0, Errors: 0
- Step 9: Commit
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:
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:
- If a project has a
sys_project_templaterow forproject.projectIdandtype, use it. - Otherwise choose the first active
sys_templatewheretemplate_type = type. - 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, usesys_project_structurenodes for the resolved template ID. -
Expand table-scoped nodes for every
GenTableinproject.getTables(). -
Step 3: Implement front preview service
IFrontProjectPreviewService:
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:
- Load
FrontProjectwith tables and columns by user/project ID. - Convert to
GenProject. - Call
genProjectService.getProjectStructure(genProject, templateType). - Call
genProjectService.previewCode(genProject, tableId, category, templateType)for file content. - Call
genProjectService.downloadStructure(genProject, templateType)for zip.
- Step 4: Add preview endpoints
In FrontProjectController:
@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:
@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:
mvn -pl ruoyi-generator -am -Dtest=FrontProjectPreviewServiceImplTest,GenProjectServiceImplTest test
Expected:
Tests run: existing generator tests plus preview test, Failures: 0, Errors: 0
- Step 7: Commit
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-webfiles listed in File Map. -
Step 1: Create package and Vite config
easycode-web/package.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:
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:
VITE_API_BASE_URL=http://localhost:8080
- Step 2: Create router guard
easycode-web/src/router/index.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:
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:
const startGenerate = () => {
router.push({
path: '/generate',
query: { keyword: keyword.value }
})
}
Login action:
router.push('/login')
- Step 5: Create generate page with mock fallback
GenerateView.vue must support these states:
const activeStep = ref(0)
const generating = ref(false)
const projectId = ref(null)
const database = ref({ tables: [], sql: '' })
Buttons:
-
“生成数据库” calls
createProject, thengenerateDatabase. -
“保存表结构” calls
saveDatabase. -
“预览项目” routes to
/project/${projectId}/preview. -
Step 6: Create preview page
PreviewView.vue loads three structures:
const templateTypes = [
{ label: '后端项目', value: 'backend' },
{ label: '前台前端', value: 'frontend' },
{ label: '后台前端', value: 'admin_frontend' }
]
Clicking a file node calls:
getFileContent(projectId, {
templateType: activeTemplateType.value,
nodeId: node.id,
tableId: node.tableId,
category: node.category
})
- Step 7: Install and build
Run:
cd easycode-web
npm install
npm run build
Expected:
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
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:
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:
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:
const res = await login(form)
setToken(res.token)
router.push(route.query.redirect || '/generate')
- Step 4: Wire generate page
On “生成数据库”:
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:
for (const item of templateTypes) {
const res = await getStructure(projectId, item.value)
structures.value[item.value] = res || []
}
Click file node:
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:
cd easycode-web
npm run build
Expected:
vite build succeeds
- Step 7: Commit
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:
# EasyCode Web
## Start
```powershell
npm install
npm run dev
Default URL: http://localhost:5174
Backend
Set VITE_API_BASE_URL in .env.development.
Flow
- Register or login front user.
- Enter project name and description.
- Generate MySQL database design through backend DeepSeek service.
- Edit tables and fields.
- Preview backend, frontend, and admin frontend project files.
- Step 2: Update root README
Add a short “前台生成工作台” section with:
### 前台生成工作台
- 前台目录:`easycode-web`
- 后台接口前缀:`/front/**`
- DeepSeek 只在后端 `AiGenerateService` 中调用
- 新增数据库脚本:`sql/front_workbench.sql`
- Step 3: Backend verification
Run:
mvn -pl ruoyi-admin -am -DfailIfNoTests=false test
Expected:
[INFO] BUILD SUCCESS
- Step 4: Frontend verification
Run:
cd easycode-web
npm run build
Expected:
vite build succeeds
- Step 5: Manual smoke test
Start backend and front app. Use these checks:
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
git add README.md easycode-web/README.md
git commit -m "Document front workbench setup"
Final Verification Before Merge
Run from repository root:
git status -sb
git diff --check
mvn -pl ruoyi-admin -am -DfailIfNoTests=false test
Run from easycode-web:
npm run build
Expected:
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_userfor front users. - Do not save AI output directly without
AiDatabaseSchemaValidator. - Keep generated draft data in
front_*tables; only convert toGenProjectin memory for preview/download.