@llryiop/avatar-boot-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +309 -0
- package/bin/cli.js +3 -0
- package/docs/plans/2026-03-12-avatar-boot-cli-design.md +73 -0
- package/docs/plans/2026-03-12-avatar-boot-cli-plan.md +681 -0
- package/package.json +28 -0
- package/src/index.js +78 -0
- package/src/prompts.js +78 -0
- package/src/template.js +37 -0
- package/src/transform.js +172 -0
- package/src/utils.js +34 -0
- package/templates/.claude/rules/architecture-redlines.md +146 -0
- package/templates/.claude/rules/code-review-standards.md +137 -0
- package/templates/.claude/rules/coding-standards.md +56 -0
- package/templates/.claude/rules/git-commit.md +59 -0
- package/templates/.claude/rules/layered-architecture.md +201 -0
- package/templates/.claude/rules/mybatis-plus.md +263 -0
- package/templates/.claude/rules/tech-stack.md +41 -0
- package/templates/.claude/rules/version.md +467 -0
- package/templates/.claude/settings.local.json +18 -0
- package/templates/.claude/skills/ai-tool-guide/SKILL.md +314 -0
- package/templates/.claude/skills/api-design/SKILL.md +200 -0
- package/templates/.claude/skills/api-doc-generator/SKILL.md +380 -0
- package/templates/.claude/skills/api-service-module-creator/SKILL.md +1114 -0
- package/templates/.claude/skills/avatar-boot-starter-feign/SKILL.md +243 -0
- package/templates/.claude/skills/avatar-boot-starter-job/SKILL.md +437 -0
- package/templates/.claude/skills/avatar-boot-starter-kafka/SKILL.md +580 -0
- package/templates/.claude/skills/avatar-boot-starter-mysql/SKILL.md +572 -0
- package/templates/.claude/skills/avatar-boot-starter-nacos/SKILL.md +901 -0
- package/templates/.claude/skills/avatar-boot-starter-oss/SKILL.md +594 -0
- package/templates/.claude/skills/avatar-boot-starter-redis/SKILL.md +586 -0
- package/templates/.claude/skills/avatar-boot-starter-rocketmq/SKILL.md +662 -0
- package/templates/.claude/skills/avatar-boot-starter-web/SKILL.md +1007 -0
- package/templates/.claude/skills/changelog-generator/SKILL.md +114 -0
- package/templates/.claude/skills/code-review/SKILL.md +239 -0
- package/templates/.claude/skills/crud-generator/SKILL.md +824 -0
- package/templates/.claude/skills/database-design/SKILL.md +377 -0
- package/templates/.claude/skills/deployment-config/SKILL.md +277 -0
- package/templates/.claude/skills/incident-analysis/SKILL.md +241 -0
- package/templates/.claude/skills/integration-test-generator/SKILL.md +496 -0
- package/templates/.claude/skills/prompt-engineering/SKILL.md +249 -0
- package/templates/.claude/skills/requirement-management/SKILL.md +244 -0
- package/templates/.claude/skills/security-audit/SKILL.md +330 -0
- package/templates/.claude/skills/test-case-design/SKILL.md +257 -0
- package/templates/.claude/skills/testing-workflow/SKILL.md +68 -0
- package/templates/.claude/skills/troubleshooting/SKILL.md +240 -0
- package/templates/CLAUDE.md +173 -0
- package/templates/README.md +303 -0
- package/templates/avatar-scaffold-api/pom.xml +41 -0
- package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/api/LoginFeignClient.java +40 -0
- package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/constant/LoginConstant.java +21 -0
- package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/dto/request/LoginRequest.java +17 -0
- package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/dto/request/RefreshTokenRequest.java +14 -0
- package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/dto/response/LoginResponse.java +31 -0
- package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/dto/response/TokenInfoResponse.java +25 -0
- package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/enums/LoginTypeEnum.java +23 -0
- package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/exception/LoginException.java +23 -0
- package/templates/avatar-scaffold-service/k8s-app/Dockerfile +14 -0
- package/templates/avatar-scaffold-service/k8s-app/Dockerfile-arm64 +14 -0
- package/templates/avatar-scaffold-service/packaging/assembly.xml +16 -0
- package/templates/avatar-scaffold-service/pom.xml +150 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/Application.java +21 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/config/LoginConfig.java +20 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/controller/LoginController.java +37 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/converter/LoginConverter.java +54 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/feign/DemoFeign.java +21 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/repository/entity/UserLoginEntity.java +33 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/repository/entity/UserTokenEntity.java +39 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/repository/mapper/UserLoginMapper.java +20 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/service/LoginService.java +22 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/service/impl/LoginServiceImpl.java +43 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/utils/LoginUtils.java +31 -0
- package/templates/avatar-scaffold-service/src/main/resources/application-dev.yaml +29 -0
- package/templates/avatar-scaffold-service/src/main/resources/application-local.yaml +61 -0
- package/templates/avatar-scaffold-service/src/main/resources/application-prod.yaml +28 -0
- package/templates/avatar-scaffold-service/src/main/resources/application-test.yaml +28 -0
- package/templates/avatar-scaffold-service/src/main/resources/application.yaml +12 -0
- package/templates/pom.xml +98 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { collectConfig } from './prompts.js';
|
|
5
|
+
import { copyTemplate } from './template.js';
|
|
6
|
+
import { transformProject } from './transform.js';
|
|
7
|
+
|
|
8
|
+
export function run() {
|
|
9
|
+
const program = new Command();
|
|
10
|
+
|
|
11
|
+
program
|
|
12
|
+
.name('avatar-boot-cli')
|
|
13
|
+
.description('CLI tool for scaffolding AvatarBoot Java microservice projects')
|
|
14
|
+
.version('1.0.0');
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.command('init [project-name]')
|
|
18
|
+
.description('Initialize a new AvatarBoot project')
|
|
19
|
+
.action(async (projectName) => {
|
|
20
|
+
console.log('');
|
|
21
|
+
console.log(chalk.blue.bold(' Avatar Boot CLI'));
|
|
22
|
+
console.log(chalk.gray(' AvatarBoot Java 微服务项目脚手架工具'));
|
|
23
|
+
console.log('');
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// 1. Collect configuration
|
|
27
|
+
const config = await collectConfig(projectName);
|
|
28
|
+
|
|
29
|
+
console.log('');
|
|
30
|
+
|
|
31
|
+
// 2. Copy template
|
|
32
|
+
const spinner = ora('正在复制模板文件...').start();
|
|
33
|
+
try {
|
|
34
|
+
copyTemplate(config.projectName);
|
|
35
|
+
spinner.succeed('模板文件复制完成');
|
|
36
|
+
} catch (err) {
|
|
37
|
+
spinner.fail('模板文件复制失败');
|
|
38
|
+
console.error(chalk.red(err.message));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 3. Transform project
|
|
43
|
+
const transformSpinner = ora('正在生成项目...').start();
|
|
44
|
+
try {
|
|
45
|
+
transformProject(config.projectName, config);
|
|
46
|
+
transformSpinner.succeed('项目生成完成');
|
|
47
|
+
} catch (err) {
|
|
48
|
+
transformSpinner.fail('项目生成失败');
|
|
49
|
+
console.error(chalk.red(err.message));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 4. Print success message
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log(chalk.green.bold(' ✅ 项目创建成功!'));
|
|
56
|
+
console.log('');
|
|
57
|
+
console.log(chalk.white(' 开始开发:'));
|
|
58
|
+
console.log(chalk.cyan(` cd ${config.projectName}`));
|
|
59
|
+
console.log(chalk.cyan(' mvn clean compile'));
|
|
60
|
+
console.log('');
|
|
61
|
+
console.log(chalk.white(' 项目结构:'));
|
|
62
|
+
console.log(chalk.gray(` ${config.projectName}/`));
|
|
63
|
+
console.log(chalk.gray(` ├── ${config.projectName}-api/ # API 模块 (接口定义)`));
|
|
64
|
+
console.log(chalk.gray(` ├── ${config.projectName}-service/ # Service 模块 (业务实现)`));
|
|
65
|
+
console.log(chalk.gray(` └── pom.xml`));
|
|
66
|
+
console.log('');
|
|
67
|
+
} catch (err) {
|
|
68
|
+
if (err.name === 'ExitPromptError') {
|
|
69
|
+
console.log(chalk.yellow('\n 已取消'));
|
|
70
|
+
} else {
|
|
71
|
+
console.error(chalk.red(`\n 错误: ${err.message}`));
|
|
72
|
+
}
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
program.parse();
|
|
78
|
+
}
|
package/src/prompts.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
|
|
3
|
+
const STARTER_MODULES = [
|
|
4
|
+
{ name: 'avatar-boot-starter-web (Web 基础设施)', value: 'web', checked: true },
|
|
5
|
+
{ name: 'avatar-boot-starter-nacos (服务注册与配置)', value: 'nacos', checked: true },
|
|
6
|
+
{ name: 'avatar-boot-starter-feign (服务间调用)', value: 'feign', checked: false },
|
|
7
|
+
{ name: 'avatar-boot-starter-mysql (MySQL 数据库)', value: 'mysql', checked: false },
|
|
8
|
+
{ name: 'avatar-boot-starter-redis (Redis 缓存)', value: 'redis', checked: false },
|
|
9
|
+
{ name: 'avatar-boot-starter-kafka (Kafka 消息队列)', value: 'kafka', checked: false },
|
|
10
|
+
{ name: 'avatar-boot-starter-rocketmq (RocketMQ 消息队列)', value: 'rocketmq', checked: false },
|
|
11
|
+
{ name: 'avatar-boot-starter-job (XXL-Job 任务调度)', value: 'job', checked: false },
|
|
12
|
+
{ name: 'avatar-boot-starter-oss (对象存储)', value: 'oss', checked: false },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
export async function collectConfig(initialName) {
|
|
16
|
+
const questions = [
|
|
17
|
+
{
|
|
18
|
+
type: 'input',
|
|
19
|
+
name: 'projectName',
|
|
20
|
+
message: '项目名称:',
|
|
21
|
+
default: initialName || 'my-avatar-service',
|
|
22
|
+
validate: (input) => /^[a-z][a-z0-9-]*$/.test(input) || '项目名称只能包含小写字母、数字和连字符,且以字母开头',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
type: 'input',
|
|
26
|
+
name: 'groupId',
|
|
27
|
+
message: 'Maven groupId:',
|
|
28
|
+
default: 'com.iflytek.avatar',
|
|
29
|
+
validate: (input) => /^[a-z][a-z0-9.]*$/.test(input) || 'groupId 格式不正确',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: 'input',
|
|
33
|
+
name: 'avatarBootVersion',
|
|
34
|
+
message: 'Avatar Boot 版本:',
|
|
35
|
+
default: '1.0.0-SNAPSHOT',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
type: 'checkbox',
|
|
39
|
+
name: 'starters',
|
|
40
|
+
message: '选择 Starter 模块:',
|
|
41
|
+
choices: STARTER_MODULES,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'confirm',
|
|
45
|
+
name: 'includeExample',
|
|
46
|
+
message: '是否生成示例代码 (login 模块):',
|
|
47
|
+
default: true,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
type: 'input',
|
|
51
|
+
name: 'nacosAddr',
|
|
52
|
+
message: 'Nacos 地址:',
|
|
53
|
+
default: '127.0.0.1:8848',
|
|
54
|
+
when: (answers) => answers.starters.includes('nacos'),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: 'input',
|
|
58
|
+
name: 'dbUrl',
|
|
59
|
+
message: '数据库地址 (host:port/dbname):',
|
|
60
|
+
default: 'localhost:3306/avatar',
|
|
61
|
+
when: (answers) => answers.starters.includes('mysql'),
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
type: 'confirm',
|
|
65
|
+
name: 'includeAiConfig',
|
|
66
|
+
message: '是否生成 .claude/ AI Agent 配置:',
|
|
67
|
+
default: true,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
type: 'input',
|
|
71
|
+
name: 'serverPort',
|
|
72
|
+
message: '服务端口:',
|
|
73
|
+
default: '8888',
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
return inquirer.prompt(questions);
|
|
78
|
+
}
|
package/src/template.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
const TEMPLATE_DIR = path.join(__dirname, '..', 'templates');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Copy the bundled template to the target directory
|
|
11
|
+
* @param {string} targetDir - Directory to copy into
|
|
12
|
+
*/
|
|
13
|
+
export function copyTemplate(targetDir) {
|
|
14
|
+
const absoluteTarget = path.resolve(targetDir);
|
|
15
|
+
|
|
16
|
+
if (fs.existsSync(absoluteTarget)) {
|
|
17
|
+
throw new Error(`Directory "${targetDir}" already exists`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
copyDirRecursive(TEMPLATE_DIR, absoluteTarget);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function copyDirRecursive(src, dest) {
|
|
24
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
25
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
26
|
+
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
const srcPath = path.join(src, entry.name);
|
|
29
|
+
const destPath = path.join(dest, entry.name);
|
|
30
|
+
|
|
31
|
+
if (entry.isDirectory()) {
|
|
32
|
+
copyDirRecursive(srcPath, destPath);
|
|
33
|
+
} else {
|
|
34
|
+
fs.copyFileSync(srcPath, destPath);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/transform.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { groupIdToPath, getAllFiles, replaceInFile, renameDir } from './utils.js';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_GROUP_ID = 'com.iflytek.avatar';
|
|
6
|
+
const DEFAULT_GROUP_PATH = 'com/iflytek/avatar';
|
|
7
|
+
const DEFAULT_ARTIFACT_ID = 'avatar-template';
|
|
8
|
+
const DEFAULT_APP_NAME = 'scaffold-template';
|
|
9
|
+
const DEFAULT_NACOS_ADDR = '172.29.242.247:8848';
|
|
10
|
+
|
|
11
|
+
const ALL_STARTERS = ['web', 'nacos', 'feign', 'mysql', 'redis', 'kafka', 'rocketmq', 'job', 'oss'];
|
|
12
|
+
|
|
13
|
+
export function transformProject(projectDir, config) {
|
|
14
|
+
const {
|
|
15
|
+
projectName,
|
|
16
|
+
groupId = DEFAULT_GROUP_ID,
|
|
17
|
+
avatarBootVersion = '1.0.0',
|
|
18
|
+
starters = ['web', 'nacos'],
|
|
19
|
+
includeExample = true,
|
|
20
|
+
nacosAddr = '127.0.0.1:8848',
|
|
21
|
+
dbUrl,
|
|
22
|
+
includeAiConfig = true,
|
|
23
|
+
serverPort = '8888',
|
|
24
|
+
} = config;
|
|
25
|
+
|
|
26
|
+
const newGroupPath = groupIdToPath(groupId);
|
|
27
|
+
const apiModule = `${projectName}-api`;
|
|
28
|
+
const serviceModule = `${projectName}-service`;
|
|
29
|
+
|
|
30
|
+
// 1. Rename module directories
|
|
31
|
+
renameDir(
|
|
32
|
+
path.join(projectDir, 'avatar-scaffold-api'),
|
|
33
|
+
path.join(projectDir, apiModule)
|
|
34
|
+
);
|
|
35
|
+
renameDir(
|
|
36
|
+
path.join(projectDir, 'avatar-scaffold-service'),
|
|
37
|
+
path.join(projectDir, serviceModule)
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// 2. Rename Java package directories if groupId changed
|
|
41
|
+
if (groupId !== DEFAULT_GROUP_ID) {
|
|
42
|
+
const apiOldPkg = path.join(projectDir, apiModule, 'src/main/java', DEFAULT_GROUP_PATH);
|
|
43
|
+
const apiNewPkg = path.join(projectDir, apiModule, 'src/main/java', newGroupPath);
|
|
44
|
+
if (fs.existsSync(apiOldPkg)) {
|
|
45
|
+
renameDir(apiOldPkg, apiNewPkg);
|
|
46
|
+
cleanEmptyDirs(path.join(projectDir, apiModule, 'src/main/java'));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const svcOldPkg = path.join(projectDir, serviceModule, 'src/main/java', DEFAULT_GROUP_PATH);
|
|
50
|
+
const svcNewPkg = path.join(projectDir, serviceModule, 'src/main/java', newGroupPath);
|
|
51
|
+
if (fs.existsSync(svcOldPkg)) {
|
|
52
|
+
renameDir(svcOldPkg, svcNewPkg);
|
|
53
|
+
cleanEmptyDirs(path.join(projectDir, serviceModule, 'src/main/java'));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 3. Remove example code if not needed
|
|
58
|
+
if (!includeExample) {
|
|
59
|
+
const apiLoginDir = path.join(projectDir, apiModule, 'src/main/java', newGroupPath, 'login');
|
|
60
|
+
const svcLoginDir = path.join(projectDir, serviceModule, 'src/main/java', newGroupPath, 'login');
|
|
61
|
+
if (fs.existsSync(apiLoginDir)) fs.rmSync(apiLoginDir, { recursive: true });
|
|
62
|
+
if (fs.existsSync(svcLoginDir)) fs.rmSync(svcLoginDir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 4. Remove .claude/ if not needed
|
|
66
|
+
if (!includeAiConfig) {
|
|
67
|
+
const claudeDir = path.join(projectDir, '.claude');
|
|
68
|
+
if (fs.existsSync(claudeDir)) fs.rmSync(claudeDir, { recursive: true });
|
|
69
|
+
const claudeMd = path.join(projectDir, 'CLAUDE.md');
|
|
70
|
+
if (fs.existsSync(claudeMd)) fs.unlinkSync(claudeMd);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 5. Replace content in all text files
|
|
74
|
+
const textExtensions = ['.xml', '.java', '.yaml', '.yml', '.properties', '.md', '.txt'];
|
|
75
|
+
const allFiles = getAllFiles(projectDir);
|
|
76
|
+
const textFiles = allFiles.filter((f) =>
|
|
77
|
+
textExtensions.some((ext) => f.endsWith(ext))
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const replacements = [
|
|
81
|
+
['avatar-scaffold-api', apiModule],
|
|
82
|
+
['avatar-scaffold-service', serviceModule],
|
|
83
|
+
['avatar-template-api', `${projectName}-api`],
|
|
84
|
+
['avatar-template-service', `${projectName}-service`],
|
|
85
|
+
['avatar-template', projectName],
|
|
86
|
+
[DEFAULT_GROUP_ID, groupId],
|
|
87
|
+
[`package ${DEFAULT_GROUP_ID}`, `package ${groupId}`],
|
|
88
|
+
[`import ${DEFAULT_GROUP_ID}`, `import ${groupId}`],
|
|
89
|
+
[DEFAULT_APP_NAME, projectName],
|
|
90
|
+
[DEFAULT_NACOS_ADDR, nacosAddr],
|
|
91
|
+
['port: 8888', `port: ${serverPort}`],
|
|
92
|
+
[`${DEFAULT_GROUP_ID}.Application`, `${groupId}.Application`],
|
|
93
|
+
['<avatar.boot.version>1.0.0-SNAPSHOT</avatar.boot.version>', `<avatar.boot.version>${avatarBootVersion}</avatar.boot.version>`],
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
for (const file of textFiles) {
|
|
97
|
+
replaceInFile(file, replacements);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 6. Handle starter dependencies in service pom.xml
|
|
101
|
+
transformServicePom(path.join(projectDir, serviceModule, 'pom.xml'), starters);
|
|
102
|
+
|
|
103
|
+
// 7. Handle database URL in config
|
|
104
|
+
if (dbUrl && starters.includes('mysql')) {
|
|
105
|
+
const devYaml = path.join(projectDir, serviceModule, 'src/main/resources/application-dev.yaml');
|
|
106
|
+
if (fs.existsSync(devYaml)) {
|
|
107
|
+
replaceInFile(devYaml, [['localhost:3306/avatar', dbUrl]]);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 8. Remove .DS_Store files
|
|
112
|
+
for (const file of allFiles) {
|
|
113
|
+
if (path.basename(file) === '.DS_Store') {
|
|
114
|
+
fs.unlinkSync(file);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function transformServicePom(pomPath, selectedStarters) {
|
|
120
|
+
if (!fs.existsSync(pomPath)) return;
|
|
121
|
+
|
|
122
|
+
let content = fs.readFileSync(pomPath, 'utf-8');
|
|
123
|
+
|
|
124
|
+
for (const starter of ALL_STARTERS) {
|
|
125
|
+
const artifactId = `avatar-boot-starter-${starter}`;
|
|
126
|
+
const depBlock = ` <dependency>\n <groupId>com.iflytek.avatar.boot</groupId>\n <artifactId>${artifactId}</artifactId>\n </dependency>`;
|
|
127
|
+
const commentedPattern = new RegExp(
|
|
128
|
+
`<!--\\s*<dependency>\\s*\\n\\s*<groupId>com\\.iflytek\\.avatar\\.boot</groupId>\\s*\\n\\s*<artifactId>${artifactId}</artifactId>\\s*\\n\\s*</dependency>\\s*-->`,
|
|
129
|
+
's'
|
|
130
|
+
);
|
|
131
|
+
const uncommentedPattern = new RegExp(
|
|
132
|
+
`<dependency>\\s*\\n\\s*<groupId>com\\.iflytek\\.avatar\\.boot</groupId>\\s*\\n\\s*<artifactId>${artifactId}</artifactId>\\s*\\n\\s*</dependency>`,
|
|
133
|
+
's'
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
if (selectedStarters.includes(starter)) {
|
|
137
|
+
const commentedMatch = content.match(commentedPattern);
|
|
138
|
+
if (commentedMatch) {
|
|
139
|
+
content = content.replace(commentedMatch[0], depBlock);
|
|
140
|
+
} else if (!uncommentedPattern.test(content)) {
|
|
141
|
+
const insertPoint = content.indexOf('</dependencies>');
|
|
142
|
+
if (insertPoint !== -1) {
|
|
143
|
+
content = content.slice(0, insertPoint) + depBlock + '\n' + content.slice(insertPoint);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
const uncommentedMatch = content.match(uncommentedPattern);
|
|
148
|
+
if (uncommentedMatch) {
|
|
149
|
+
content = content.replace(
|
|
150
|
+
uncommentedMatch[0],
|
|
151
|
+
`<!--${uncommentedMatch[0]}-->`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
fs.writeFileSync(pomPath, content, 'utf-8');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function cleanEmptyDirs(dir) {
|
|
161
|
+
if (!fs.existsSync(dir)) return;
|
|
162
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
163
|
+
for (const entry of entries) {
|
|
164
|
+
if (entry.isDirectory()) {
|
|
165
|
+
cleanEmptyDirs(path.join(dir, entry.name));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const remaining = fs.readdirSync(dir);
|
|
169
|
+
if (remaining.length === 0) {
|
|
170
|
+
fs.rmdirSync(dir);
|
|
171
|
+
}
|
|
172
|
+
}
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export function groupIdToPath(groupId) {
|
|
5
|
+
return groupId.replace(/\./g, '/');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function getAllFiles(dir, fileList = []) {
|
|
9
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
10
|
+
for (const entry of entries) {
|
|
11
|
+
const fullPath = path.join(dir, entry.name);
|
|
12
|
+
if (entry.isDirectory()) {
|
|
13
|
+
getAllFiles(fullPath, fileList);
|
|
14
|
+
} else {
|
|
15
|
+
fileList.push(fullPath);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return fileList;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function replaceInFile(filePath, replacements) {
|
|
22
|
+
let content = fs.readFileSync(filePath, 'utf-8');
|
|
23
|
+
for (const [search, replace] of replacements) {
|
|
24
|
+
content = content.replaceAll(search, replace);
|
|
25
|
+
}
|
|
26
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function renameDir(oldPath, newPath) {
|
|
30
|
+
if (fs.existsSync(oldPath)) {
|
|
31
|
+
fs.mkdirSync(path.dirname(newPath), { recursive: true });
|
|
32
|
+
fs.renameSync(oldPath, newPath);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# 架构红线与通信协议
|
|
2
|
+
|
|
3
|
+
> 规则类型: 🔴 强制执行(不可违反)
|
|
4
|
+
> 更新频率: 极低(架构变更时更新)
|
|
5
|
+
> 相关规则: [tech-stack.md](tech-stack.md) | [coding-standards.md](coding-standards.md) | [version.md](version.md)
|
|
6
|
+
> 相关技能: [api-service-module-creator](../skills/api-service-module-creator/)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 依赖管理红线
|
|
11
|
+
|
|
12
|
+
> **完整规则详见**: [version.md](version.md) 和 [tech-stack.md](tech-stack.md)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 架构设计红线
|
|
17
|
+
|
|
18
|
+
### 4. API 与 Service 必须分离
|
|
19
|
+
- API 模块只包含接口定义、DTO、Feign 客户端
|
|
20
|
+
- Service 模块包含具体实现、业务逻辑、数据访问
|
|
21
|
+
|
|
22
|
+
### 5. 功能模块必须垂直划分
|
|
23
|
+
- 每个业务功能(如 login、user)独立成包
|
|
24
|
+
- 功能包内包含该功能的所有层次(controller、service、dao)
|
|
25
|
+
- 禁止水平分层(不要创建全局的 controller、service、dao 包)
|
|
26
|
+
|
|
27
|
+
### 6. 统一返回格式
|
|
28
|
+
- 所有 Controller 方法必须返回 `Result<T>` 类型
|
|
29
|
+
- 使用 `com.iflytek.avatar.boot.entity.response.Result` 类
|
|
30
|
+
- 禁止直接返回业务对象或自定义响应格式
|
|
31
|
+
|
|
32
|
+
### 7. 服务间调用必须通过 Feign
|
|
33
|
+
- 使用 `avatar-boot-starter-feign` 实现服务间调用
|
|
34
|
+
- 禁止使用 RestTemplate 或 HttpClient 直接调用
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 安全红线
|
|
39
|
+
|
|
40
|
+
### 8. 敏感信息禁止硬编码
|
|
41
|
+
- 数据库密码、API 密钥等必须通过 Nacos 配置中心管理
|
|
42
|
+
- 禁止在代码中硬编码任何敏感信息
|
|
43
|
+
|
|
44
|
+
### 9. 高危漏洞必须立即修复
|
|
45
|
+
- CVE 评分 >= 7.0 的漏洞必须立即处理
|
|
46
|
+
- 定期运行 `mvn org.owasp:dependency-check-maven:check`
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## REST API 通信规范
|
|
51
|
+
|
|
52
|
+
- **请求方法**: GET(查询)、POST(创建)、PUT(更新)、DELETE(删除)
|
|
53
|
+
- **URL 路径**: 使用 kebab-case,如 `/api/user-info`
|
|
54
|
+
- **请求体**: JSON 格式,字段使用 camelCase
|
|
55
|
+
- **响应格式**: 统一使用 `Result<T>` 包装
|
|
56
|
+
|
|
57
|
+
### Result 响应格式
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
// 成功响应
|
|
61
|
+
{
|
|
62
|
+
"code": 200,
|
|
63
|
+
"message": "success",
|
|
64
|
+
"data": { }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 失败响应
|
|
68
|
+
{
|
|
69
|
+
"code": 400,
|
|
70
|
+
"message": "参数错误",
|
|
71
|
+
"data": null
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Result 使用示例
|
|
76
|
+
|
|
77
|
+
```java
|
|
78
|
+
@RestController
|
|
79
|
+
@RequestMapping("/api/user")
|
|
80
|
+
public class UserController {
|
|
81
|
+
|
|
82
|
+
@GetMapping("/{id}")
|
|
83
|
+
public Result<UserResponse> getUser(@PathVariable Long id) {
|
|
84
|
+
UserResponse user = userService.getById(id);
|
|
85
|
+
return Result.success(user);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@PostMapping
|
|
89
|
+
public Result<Void> createUser(@RequestBody UserRequest request) {
|
|
90
|
+
userService.create(request);
|
|
91
|
+
return Result.success();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@ExceptionHandler(Exception.class)
|
|
95
|
+
public Result<Void> handleException(Exception e) {
|
|
96
|
+
return Result.fail(500, e.getMessage());
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 目录组织规范
|
|
104
|
+
|
|
105
|
+
### API 模块(avatar-scaffold-api)包结构
|
|
106
|
+
|
|
107
|
+
- `api/` - 接口定义(供外部调用的 REST API 接口)
|
|
108
|
+
- `constant/` - 常量定义(业务常量、配置常量)
|
|
109
|
+
- `enums/` - 枚举定义(业务枚举类型)
|
|
110
|
+
- `exception/` - 异常定义(业务异常类)
|
|
111
|
+
- `entity/request/` - 请求参数实体(DTO,用于接收客户端请求)
|
|
112
|
+
- `entity/response/` - 响应结果实体(DTO,必须使用 `Result<T>` 包装)
|
|
113
|
+
- `feign/` - Feign 客户端(用于服务间调用)
|
|
114
|
+
|
|
115
|
+
### Service 模块(avatar-scaffold-service)包结构
|
|
116
|
+
|
|
117
|
+
- `config/` - 配置类(Spring 配置、Bean 定义)
|
|
118
|
+
- `controller/` - REST 控制器(实现 API 接口,返回 `Result<T>`)
|
|
119
|
+
- `service/` - 业务逻辑层(Service 接口和实现类)
|
|
120
|
+
- `dao/` - 数据访问层(MyBatis Mapper 接口)
|
|
121
|
+
- `dao/beans/` - 数据库实体类(DO/PO,对应数据库表结构)
|
|
122
|
+
- `utils/` - 工具类(功能模块内部使用)
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 违规检查清单
|
|
127
|
+
|
|
128
|
+
### 架构检查
|
|
129
|
+
- [ ] 是否遵循 API + Service 双模块结构
|
|
130
|
+
- [ ] 是否按功能垂直划分包结构
|
|
131
|
+
- [ ] Controller 是否返回 `Result<T>` 类型
|
|
132
|
+
- [ ] 是否使用 Feign 进行服务间调用
|
|
133
|
+
|
|
134
|
+
### 代码检查
|
|
135
|
+
- [ ] 是否使用了 `jakarta.*` 命名空间
|
|
136
|
+
- [ ] 是否有未处理的异常
|
|
137
|
+
- [ ] 是否有硬编码的敏感信息
|
|
138
|
+
- [ ] 是否有 `System.out.println()`
|
|
139
|
+
|
|
140
|
+
### 依赖检查
|
|
141
|
+
> **详细的依赖检查命令和工具请参考**: [version.md](version.md)
|
|
142
|
+
|
|
143
|
+
- [ ] 是否使用了禁止的依赖(如 `javax.*`)
|
|
144
|
+
- [ ] 是否在子模块中指定了版本
|
|
145
|
+
- [ ] 是否直接引用了 Spring Boot 原生 starter
|
|
146
|
+
- [ ] 是否配置了 iFlytek Maven 仓库
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# 代码审查标准 (Code Review Standards)
|
|
2
|
+
|
|
3
|
+
> 🔴 **强制执行** — 所有代码合并前必须通过代码审查
|
|
4
|
+
> 📎 关联规则: [coding-standards.md](./coding-standards.md), [architecture-redlines.md](./architecture-redlines.md)
|
|
5
|
+
> 📋 适用范围: 所有 Java 源代码、配置文件、SQL 脚本
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 审查维度
|
|
10
|
+
|
|
11
|
+
代码审查覆盖以下五个核心维度,按优先级排序:
|
|
12
|
+
|
|
13
|
+
1. **正确性 (Correctness)** — 代码逻辑是否正确,是否满足需求
|
|
14
|
+
2. **安全性 (Security)** — 是否存在安全漏洞或敏感数据泄露
|
|
15
|
+
3. **性能 (Performance)** — 是否存在性能隐患或资源浪费
|
|
16
|
+
4. **可维护性 (Maintainability)** — 代码是否清晰、易读、易扩展
|
|
17
|
+
5. **测试覆盖 (Test Coverage)** — 核心逻辑是否有单元测试覆盖
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 严重等级定义
|
|
22
|
+
|
|
23
|
+
| 等级 | 标识 | 含义 | 处理要求 |
|
|
24
|
+
|------|------|------|----------|
|
|
25
|
+
| **Blocker** | 🚫 | 阻塞性问题,存在严重 Bug 或安全漏洞 | 必须修复后才能合并 |
|
|
26
|
+
| **Major** | ⚠️ | 重要问题,影响代码质量或潜在风险 | 应当修复,特殊情况需说明理由 |
|
|
27
|
+
| **Minor** | 💡 | 次要问题,代码风格或小优化 | 建议修复 |
|
|
28
|
+
| **Suggestion** | 📝 | 可选改进,更好的实现方式 | 可选采纳 |
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 分层审查清单
|
|
33
|
+
|
|
34
|
+
> 各层详细规范和代码示例请参考 [layered-architecture.md](layered-architecture.md) 和 [mybatis-plus.md](mybatis-plus.md)
|
|
35
|
+
|
|
36
|
+
### Controller 层
|
|
37
|
+
|
|
38
|
+
- [ ] 使用 `Result<T>` 统一返回格式
|
|
39
|
+
- [ ] 参数校验注解完整 (`@Valid`, `@NotNull`, `@NotBlank`)
|
|
40
|
+
- [ ] 使用 Request/Response DTO,禁止直接暴露 Entity
|
|
41
|
+
- [ ] 无业务逻辑,仅做参数校验和 Service 调用
|
|
42
|
+
- [ ] 异常由全局异常处理器处理,不做 try-catch
|
|
43
|
+
|
|
44
|
+
### Service 层
|
|
45
|
+
|
|
46
|
+
- [ ] 继承 `IService<T>` / `ServiceImpl<M, T>`
|
|
47
|
+
- [ ] 写操作加 `@Transactional(rollbackFor = Exception.class)`
|
|
48
|
+
- [ ] 只读操作加 `@Transactional(readOnly = true)`
|
|
49
|
+
- [ ] 事务范围最小化,不包含 RPC / 文件 IO
|
|
50
|
+
- [ ] 业务异常使用自定义异常类
|
|
51
|
+
|
|
52
|
+
### Mapper 层
|
|
53
|
+
|
|
54
|
+
- [ ] 继承 `BaseMapper<T>`
|
|
55
|
+
- [ ] 自定义 SQL 使用 `#{}` 占位符,禁止 `${}`
|
|
56
|
+
- [ ] 分页使用 MyBatis Plus 分页插件
|
|
57
|
+
- [ ] 批量操作使用 `saveBatch` / `updateBatchById`
|
|
58
|
+
|
|
59
|
+
### Entity 层
|
|
60
|
+
|
|
61
|
+
- [ ] `@TableName`、`@TableId(type = IdType.ASSIGN_ID)` 注解完整
|
|
62
|
+
- [ ] `createTime`/`updateTime` 自动填充,`isDeleted` 逻辑删除
|
|
63
|
+
- [ ] Request DTO 在 `entity/request/`,Response DTO 在 `entity/response/`
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 安全审查项
|
|
68
|
+
|
|
69
|
+
> 完整的 OWASP Top 10 检查清单和修复方案请参考 [security-audit Skill](../skills/security-audit/)
|
|
70
|
+
|
|
71
|
+
### 核心检查项
|
|
72
|
+
|
|
73
|
+
- [ ] MyBatis 禁止 `${}` 拼接用户输入,必须用 `#{}`
|
|
74
|
+
- [ ] 密码字段不在 Response DTO 中返回
|
|
75
|
+
- [ ] 日志中不打印敏感信息(密码、手机号、身份证号)
|
|
76
|
+
- [ ] 接口有权限校验,数据有归属校验
|
|
77
|
+
- [ ] 配置文件中的密码使用加密存储
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 性能审查项
|
|
82
|
+
|
|
83
|
+
### 数据库性能
|
|
84
|
+
|
|
85
|
+
- [ ] **无 N+1 查询问题** — 循环中不调用数据库查询
|
|
86
|
+
- [ ] 查询条件字段有索引覆盖
|
|
87
|
+
- [ ] 分页查询有 `LIMIT` 限制,禁止无限制全表扫描
|
|
88
|
+
- [ ] 大数据量查询使用流式处理或分批处理
|
|
89
|
+
- [ ] 批量插入使用 `saveBatch`,禁止循环单条插入
|
|
90
|
+
|
|
91
|
+
✅ 正确示例:
|
|
92
|
+
```java
|
|
93
|
+
// 批量查询后在内存中关联
|
|
94
|
+
List<User> users = userService.listByIds(userIds);
|
|
95
|
+
Map<Long, User> userMap = users.stream()
|
|
96
|
+
.collect(Collectors.toMap(User::getId, Function.identity()));
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
❌ 错误示例:
|
|
100
|
+
```java
|
|
101
|
+
// N+1 查询:循环中逐条查询
|
|
102
|
+
for (Order order : orders) {
|
|
103
|
+
User user = userService.getById(order.getUserId()); // 每次循环都查库
|
|
104
|
+
order.setUsername(user.getUsername());
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 连接与资源
|
|
109
|
+
|
|
110
|
+
- [ ] 数据库连接及时释放,无连接泄露
|
|
111
|
+
- [ ] 流资源在 `try-with-resources` 中使用
|
|
112
|
+
- [ ] 缓存使用合理,有过期策略
|
|
113
|
+
- [ ] 线程池参数配置合理
|
|
114
|
+
|
|
115
|
+
### 接口性能
|
|
116
|
+
|
|
117
|
+
- [ ] 接口响应时间合理(普通接口 < 500ms)
|
|
118
|
+
- [ ] 大文件上传使用流式处理
|
|
119
|
+
- [ ] 列表接口强制分页
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 最终审查清单
|
|
124
|
+
|
|
125
|
+
> **审查报告模板、评论规范和工作流详见**: [code-review Skill](../skills/code-review/)
|
|
126
|
+
|
|
127
|
+
在点击「Approve」之前,确认以下所有项目:
|
|
128
|
+
|
|
129
|
+
- [ ] 所有 Blocker 问题已修复
|
|
130
|
+
- [ ] 所有 Major 问题已修复或已记录 TODO
|
|
131
|
+
- [ ] 代码符合分层架构规范(参考 architecture-redlines.md)
|
|
132
|
+
- [ ] 代码符合编码规范(参考 coding-standards.md)
|
|
133
|
+
- [ ] 无安全漏洞(SQL 注入、XSS、敏感数据泄露)
|
|
134
|
+
- [ ] 无明显性能问题(N+1、全表扫描、连接泄露)
|
|
135
|
+
- [ ] 单元测试覆盖核心业务逻辑
|
|
136
|
+
- [ ] API 文档已更新
|
|
137
|
+
- [ ] 数据库变更有对应的 migration 脚本
|