Compare commits

..

No commits in common. 'main' and 'guoning' have entirely different histories.

51
src/.gitignore vendored

@ -1,51 +0,0 @@
# Node.js
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# 项目特定
projects_data/
out/
*.log
# 临时文件
*.tmp
*.temp
temp_*.py
.DS_Store
Thumbs.db
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# 环境变量
.env
.env.local

@ -0,0 +1,151 @@
# 代码检查功能使用说明
## 功能概述
FortifyCode 系统现在支持真实的代码质量检查集成了三个主要的Python代码分析工具
- **pylint**: 代码质量分析工具
- **flake8**: 代码风格检查工具
- **bandit**: 安全漏洞检测工具
## 使用方法
### 1. 启动系统
```bash
# 启动后端服务
cd FortifyCode/backend
python app.py
# 访问前端界面
# 打开浏览器访问 http://localhost:5000
```
### 2. 上传代码文件
1. 在仪表板页面,点击"拖拽文件到此处或点击上传"区域
2. 选择要检查的Python文件支持 .py, .pyx, .pyi 格式)
3. 系统会显示已选择的文件数量
### 3. 开始检查
1. 点击"开始检查"按钮
2. 系统会显示检查进度:
- 正在上传文件...
- 开始代码检查...
- 处理检查结果...
- 检查完成!
### 4. 查看结果
检查完成后,系统会显示:
- **结果摘要**: 显示错误、警告、信息的总数统计
- **问题列表**: 详细列出每个发现的问题
- **工具状态**: 显示各个Agentpylint、flake8、bandit的执行状态
### 5. 问题详情
对于每个发现的问题,可以:
- **查看详情**: 点击"查看详情"按钮查看问题的详细信息
- **修复建议**: 点击"修复建议"按钮获取具体的修复建议
## 检查规则
### pylint 检查项
- 代码风格问题
- 潜在的逻辑错误
- 代码复杂度
- 命名规范
### flake8 检查项
- 行长度限制
- 导入顺序
- 未使用的变量
- 语法错误
### bandit 检查项
- SQL注入风险
- 硬编码密码
- 不安全的函数调用
- 安全配置问题
## 项目管理功能
### 新建项目
1. 在项目管理页面点击"新建项目"按钮
2. 填写项目信息:
- 项目名称(必填)
- 项目描述(可选)
- 项目来源GitHub、Gitee 或文件上传
- 如果选择 Git 来源,需要提供仓库 URL
3. 点击"创建项目"完成创建
### 删除项目
1. 在项目卡片上点击"删除"按钮
2. 在项目详情页面点击"删除项目"按钮
3. 确认删除操作(此操作不可撤销)
4. 系统将删除:
- 项目信息和配置
- 所有检查历史记录
- 所有检查结果数据
- 项目本地文件
### 项目操作
- **查看详情**:点击项目卡片或"查看详情"按钮
- **运行检查**:在项目列表或详情页面点击"运行检查"按钮
- **搜索项目**:使用搜索框按项目名称或描述搜索
### 文件管理功能
#### 文件浏览器
在项目详情页面,您可以:
- **浏览文件**:点击文件夹进入目录,点击文件打开编辑
- **查看文件信息**:显示文件大小、修改时间等信息
- **文件类型识别**:不同文件类型显示不同颜色的图标
#### 代码编辑器
- **打开文件**:点击文件即可在编辑器中打开
- **编辑代码**:支持语法高亮和实时编辑
- **保存文件**:使用 Ctrl+S 快捷键或点击保存按钮
- **关闭编辑器**:点击关闭按钮(如有未保存更改会提示)
#### 文件操作
- **上传文件**:点击"上传文件"按钮选择本地文件上传
- **新建文件**:点击"新建文件"按钮创建新文件
- **新建文件夹**:点击"新建文件夹"按钮创建新目录
- **下载文件**通过API直接下载项目文件
#### 支持的编辑功能
- 实时文件内容编辑
- 自动保存提示
- 键盘快捷键支持Ctrl+S 保存)
- 文件修改状态跟踪
- 多文件类型支持Python、JavaScript、CSS、HTML等
## 测试文件
系统包含一个测试文件 `test_sample.py`,其中包含各种常见的代码问题,可以用来测试检查功能。
## 注意事项
1. 确保系统已安装所需的Python包
```bash
pip install pylint flake8 bandit
```
2. 检查过程可能需要一些时间,特别是对于大型项目
3. 检查结果会保存到 `reports/` 目录下,文件名格式为 `check_{check_id}_{timestamp}.json`
4. 临时上传的文件会在检查完成后自动清理
## 故障排除
如果检查失败,请检查:
1. 后端服务是否正常运行
2. 是否安装了所有必需的Python包
3. 上传的文件是否为有效的Python文件
4. 查看浏览器控制台的错误信息

@ -0,0 +1,235 @@
# 基于多agent协同的军工Python代码合规性检查系统 - FortifyCode
## 🎯 系统特色
### 核心功能
- **多 Agent 协同检查**: 集成 pylint、flake8、bandit 三大专业工具
- **项目导入管理**: 支持 GitHub、Gitee 仓库导入和本地文件上传
- **实时检查报告**: 详细的代码质量、安全漏洞、合规性分析
- **军事级安全**: 专门针对军事代码的特殊安全要求
### 技术架构
- **前端**: HTML5 + CSS3 + JavaScript (ES6+)
- **后端**: Flask + SQLAlchemy + MySQL
- **代码检查**: pylint + flake8 + bandit
- **版本控制**: Git 集成支持
## 🚀 快速开始
### 环境要求
- Python 3.8+
- MySQL 5.7+
- Node.js (可选,用于前端开发)
### 安装步骤
1. **克隆项目**
```bash
git clone <repository-url>
cd FortifyCode
```
2. **安装 Python 依赖**
```bash
cd backend
pip install -r requirements.txt
```
3. **安装代码检查工具**
```bash
pip install pylint flake8 bandit
```
4. **配置数据库**
```bash
# 启动 MySQL 服务
# 创建数据库用户 (可选)
mysql -u root -p
SOURCE database_init.sql
```
5. **启动系统**
```bash
python app.py
```
6. **访问系统**
- 前端界面: http://localhost:3000 (或直接打开 index.html)
- 后端API: http://localhost:5000
## 📁 项目结构
```
FortifyCode/
├── index.html # 主页面
├── css/
│ └── style.css # 样式文件
├── js/
│ └── app.js # 前端逻辑
├── backend/
│ ├── app.py # Flask 后端服务
│ ├── config.py # 配置文件
│ ├── start.py # 启动脚本
│ ├── requirements.txt # Python 依赖
│ └── database_init.sql # 数据库初始化脚本
├── uploads/ # 文件上传目录
├── projects/ # 项目存储目录
├── reports/ # 检查报告目录
└── README.md # 项目说明
```
## 🔧 配置说明
### 数据库配置
`backend/config.py` 中修改数据库连接信息:
```python
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://用户名:密码@localhost/fortifycode'
```
### 工具配置
`backend/app.py` 中修改工具配置:
```python
TOOLS_CONFIG = {
'pylint': {
'command': 'pylint',
'args': ['--output-format=json', '--reports=no'],
'enabled': True
},
# ... 其他工具配置
}
```
## 📖 使用指南
### 1. 项目管理
- **新建项目**: 点击"新建项目"按钮,选择项目来源
- GitHub/Gitee: 输入仓库 URL
- 文件上传: 选择本地文件或文件夹
- **项目列表**: 查看所有项目的基本信息和检查状态
- **项目搜索**: 使用搜索框快速查找项目
### 2. 代码检查
- **快捷检查**: 在仪表板直接上传文件进行快速检查
- **项目检查**: 在项目详情页面运行完整的代码检查
- **检查历史**: 查看项目的所有检查记录
### 3. 报告查看
- **检查结果**: 查看详细的代码问题列表
- **问题分类**: 按错误、警告、信息分类查看
- **修复建议**: 获取具体的问题修复建议
### 4. 系统设置
- **Agent配置**: 配置各个检查工具的参数
- **检查规则**: 自定义代码风格和安全规则
- **通知设置**: 配置检查完成通知
## 🛡️ 安全特性
### 军事级安全要求
- **敏感信息检测**: 自动识别硬编码密码、API密钥等
- **安全漏洞扫描**: SQL注入、XSS、CSRF等安全漏洞检测
- **合规性检查**: 军事代码规范合规性验证
- **访问控制**: 用户权限管理和操作日志记录
### 数据安全
- **文件隔离**: 每个项目独立存储,避免数据泄露
- **临时文件清理**: 自动清理检查过程中的临时文件
- **数据加密**: 敏感数据加密存储
## 🔍 检查工具说明
### pylint
- **功能**: Python 代码质量检查
- **检查项**: 代码风格、错误检测、复杂度分析
- **输出**: JSON 格式的结构化报告
### flake8
- **功能**: Python 代码风格检查
- **检查项**: PEP8 规范、语法错误、未使用变量
- **输出**: 文本格式的详细报告
### bandit
- **功能**: Python 安全漏洞扫描
- **检查项**: 安全漏洞、敏感信息泄露、危险函数调用
- **输出**: JSON 格式的安全报告
## 📊 报告格式
### 检查结果结构
```json
{
"total_issues": 25,
"error_count": 5,
"warning_count": 15,
"info_count": 5,
"tools_status": {
"pylint": "completed",
"flake8": "completed",
"bandit": "completed"
},
"all_issues": [
{
"file": "src/database.py",
"line": 45,
"type": "error",
"severity": "high",
"message": "SQL注入风险",
"rule": "B608"
}
]
}
```
## 🚨 故障排除
### 常见问题
1. **MySQL 连接失败**
- 检查 MySQL 服务是否启动
- 验证用户名和密码是否正确
- 确认数据库是否存在
2. **代码检查工具未找到**
- 运行 `pip install pylint flake8 bandit`
- 检查 PATH 环境变量
- 使用绝对路径配置工具
3. **文件上传失败**
- 检查 uploads 目录权限
- 确认文件大小限制
- 验证文件格式是否支持
4. **前端页面无法访问**
- 检查文件路径是否正确
- 确认浏览器支持现代 JavaScript
- 查看浏览器控制台错误信息
### 日志查看
系统日志输出在控制台,包含:
- 请求处理日志
- 错误信息
- 检查进度信息
## 🤝 贡献指南
1. Fork 项目
2. 创建功能分支 (`git checkout -b feature/AmazingFeature`)
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 打开 Pull Request
## 📄 许可证
本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情
## 📞 支持
如有问题或建议,请:
- 提交 Issue
- 发送邮件至项目维护者
- 查看项目文档
---
**FortifyCode** - 让代码更安全,让开发更放心 🛡️

File diff suppressed because it is too large Load Diff

@ -0,0 +1,30 @@
多Agent状态面板
<div class="agent-status">
<div class="panel-header">
<h2 class="panel-title">Agent 状态</h2>
</div>
<div class="agent-list">
<div class="agent-item">
<div class="agent-avatar">A1</div>
<div class="agent-info">
<div class="agent-name">pylint Agent</div>
<span class="agent-status-badge status-idle">空闲</span>
</div>
</div>
<div class="agent-item">
<div class="agent-avatar">A2</div>
<div class="agent-info">
<div class="agent-name">flake8 Agent</div>
<span class="agent-status-badge status-idle">空闲</span>
</div>
</div>
<div class="agent-item">
<div class="agent-avatar">A3</div>
<div class="agent-info">
<div class="agent-name">bandit Agent</div>
<span class="agent-status-badge status-idle">空闲</span>
</div>
</div>
</div>
</div>
</div>

@ -0,0 +1,70 @@
-- 设置字符集
SET NAMES utf8mb4;
SET CHARACTER SET utf8mb4;
-- 创建数据库
CREATE DATABASE IF NOT EXISTS fortifycode CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 使用数据库
USE fortifycode;
-- 创建项目表
CREATE TABLE IF NOT EXISTS project (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(200) NOT NULL,
description TEXT,
source_type VARCHAR(50) NOT NULL COMMENT 'github, gitee, upload',
source_url VARCHAR(500),
local_path VARCHAR(500),
language VARCHAR(50) DEFAULT 'python',
status VARCHAR(50) DEFAULT 'active' COMMENT 'active, archived, deleted',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_by VARCHAR(100) DEFAULT 'admin',
INDEX idx_name (name),
INDEX idx_status (status),
INDEX idx_created_at (created_at)
);
-- 创建代码检查表
CREATE TABLE IF NOT EXISTS code_check (
id INT AUTO_INCREMENT PRIMARY KEY,
project_id INT NOT NULL,
check_type VARCHAR(50) NOT NULL COMMENT 'pylint, flake8, bandit, combined',
status VARCHAR(50) DEFAULT 'pending' COMMENT 'pending, running, completed, failed',
started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
completed_at TIMESTAMP NULL,
total_files INT DEFAULT 0,
total_issues INT DEFAULT 0,
error_count INT DEFAULT 0,
warning_count INT DEFAULT 0,
info_count INT DEFAULT 0,
report_path VARCHAR(500),
summary TEXT,
FOREIGN KEY (project_id) REFERENCES project(id) ON DELETE CASCADE,
INDEX idx_project_id (project_id),
INDEX idx_status (status),
INDEX idx_started_at (started_at)
);
-- 创建检查结果表
CREATE TABLE IF NOT EXISTS check_result (
id INT AUTO_INCREMENT PRIMARY KEY,
check_id INT NOT NULL,
file_path VARCHAR(500) NOT NULL,
line_number INT,
column_number INT,
issue_type VARCHAR(50) NOT NULL COMMENT 'error, warning, info',
severity VARCHAR(20) DEFAULT 'medium' COMMENT 'high, medium, low',
rule_id VARCHAR(100),
message TEXT NOT NULL,
suggestion TEXT,
FOREIGN KEY (check_id) REFERENCES code_check(id) ON DELETE CASCADE,
INDEX idx_check_id (check_id),
INDEX idx_issue_type (issue_type),
INDEX idx_severity (severity)
);
-- 字符集设置正确
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;

@ -0,0 +1,69 @@
# 党费管理系统
这是一个基于Flask的党费管理系统用于管理党员的党费缴纳情况。
## 功能特点
- 用户登录和权限管理
- 党费记录管理(添加、修改、删除)
- 数据统计和分析
- 数据导入导出
- 响应式界面设计
## 系统要求
- Python 3.7+
- MySQL 5.7+
- 现代浏览器Chrome、Firefox、Safari等
## 安装步骤
1. 克隆项目到本地
2. 创建虚拟环境:
```bash
python -m venv venv
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
```
3. 安装依赖:
```bash
pip install -r requirements.txt
```
4. 创建数据库:
```bash
mysql -u root -p < manage.sql
```
5. 修改数据库配置:
在server.py中修改DB_CONFIG配置
## 运行系统
```bash
python server.py
```
访问 http://localhost:5000 即可使用系统
## 默认账号
- 管理员账号admin
- 密码a123
## 使用说明
1. 管理员功能:
- 管理所有用户的党费记录
- 查看统计数据
- 导入导出数据
- 管理用户信息
2. 普通用户功能:
- 查看个人党费记录
- 修改个人信息
- 查看缴费状态
## 注意事项
- 请定期备份数据库
- 及时更新系统密码
- 定期检查系统日志

@ -0,0 +1,92 @@
-- 创建数据库
CREATE DATABASE IF NOT EXISTS df2_0 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE df2_0;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码',
`name` varchar(50) NOT NULL COMMENT '姓名',
`gender` varchar(10) NOT NULL COMMENT '性别',
`unit` varchar(100) NOT NULL COMMENT '单位',
`role` varchar(20) NOT NULL DEFAULT 'user' COMMENT '角色',
`contact` varchar(50) DEFAULT NULL COMMENT '联系方式',
`last_login` datetime DEFAULT NULL COMMENT '最后登录时间',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- ----------------------------
-- Table structure for dues
-- ----------------------------
DROP TABLE IF EXISTS `dues`;
CREATE TABLE `dues` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL COMMENT '用户ID',
`due_amount` decimal(10,2) NOT NULL COMMENT '应缴金额',
`actual_amount` decimal(10,2) NOT NULL COMMENT '实缴金额',
`payment_type` varchar(20) NOT NULL COMMENT '缴费类型',
`payment_date` date NOT NULL COMMENT '缴费日期',
`status` varchar(20) NOT NULL COMMENT '状态',
`remark` text COMMENT '备注',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
CONSTRAINT `dues_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='党费记录表';
-- ----------------------------
-- Table structure for payment_types
-- ----------------------------
DROP TABLE IF EXISTS `payment_types`;
CREATE TABLE `payment_types` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type_code` varchar(20) NOT NULL COMMENT '类型代码',
`type_name` varchar(50) NOT NULL COMMENT '类型名称',
`description` varchar(200) DEFAULT NULL COMMENT '描述',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `type_code` (`type_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='缴费类型表';
-- ----------------------------
-- Table structure for system_logs
-- ----------------------------
DROP TABLE IF EXISTS `system_logs`;
CREATE TABLE `system_logs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL COMMENT '用户ID',
`action` varchar(50) NOT NULL COMMENT '操作类型',
`description` text NOT NULL COMMENT '操作描述',
`ip_address` varchar(50) DEFAULT NULL COMMENT 'IP地址',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
CONSTRAINT `system_logs_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统日志表';
-- ----------------------------
-- Records of users
-- ----------------------------
INSERT INTO `users` VALUES (1, 'admin', 'a123', '张三', '', '系统管理部', 'admin', 'admin@example.com', NOW(), NOW(), NOW());
-- ----------------------------
-- Records of payment_types
-- ----------------------------
INSERT INTO `payment_types` VALUES
(1, 'monthly', '月度缴费', '每月固定党费缴纳', NOW()),
(2, 'special', '特殊党费', '特殊时期或特殊用途的党费缴纳', NOW()),
(3, 'other', '其他', '其他类型的党费缴纳', NOW());
SET FOREIGN_KEY_CHECKS = 1;

@ -0,0 +1,5 @@
Flask==2.0.1
Flask-Cors==3.0.10
PyMySQL==1.0.2
python-dotenv==0.19.0
Werkzeug==2.0.1

@ -0,0 +1,692 @@
# Flask框架相关模块导入
from flask import (
Flask, # Flask应用主类用于创建Web应用实例
request, # 请求对象,用于获取客户端请求数据
jsonify, # 将Python对象转换为JSON响应
render_template,# 渲染HTML模板
session, # 会话管理,用于存储用户会话数据
redirect, # 重定向响应
url_for # 生成URL
)
# Flask-CORS用于处理跨域请求
from flask_cors import CORS
# PyMySQL用于MySQL数据库连接和操作
import pymysql
# json模块用于JSON数据处理
import json
# datetime用于处理日期和时间
from datetime import datetime
# os模块用于操作系统相关功能
import os
# functools.wraps用于保留被装饰函数的元数据
from functools import wraps
# decimal模块用于处理Decimal类型
import decimal
# 创建Flask应用实例
app = Flask(__name__)
# 启用CORS支持允许跨域请求
CORS(app)
# 设置会话密钥用于加密session数据
app.secret_key = os.urandom(24)
# 数据库连接配置
DB_CONFIG = {
'host': 'localhost', # 数据库主机地址
'user': 'root', # 数据库用户名
'password': '123456', # 数据库密码
'db': 'df2_0', # 数据库名称
'charset': 'utf8mb4' # 字符集设置,支持中文
}
# 获取数据库连接的函数
def get_db():
"""
创建并返回数据库连接
使用PyMySQL连接MySQL数据库
"""
return pymysql.connect(**DB_CONFIG)
# 登录验证装饰器
def login_required(f):
"""
登录验证装饰器
用于保护需要登录才能访问的路由
"""
@wraps(f) # 保留被装饰函数的元数据
def decorated_function(*args, **kwargs):
# 检查session中是否存在user_id
if 'user_id' not in session:
# 如果未登录,重定向到登录页面
return redirect(url_for('login'))
return f(*args, **kwargs)
return decorated_function
# 根路径路由处理
@app.route('/')
def index():
"""
根路径处理函数
根据用户登录状态和角色重定向到相应页面
"""
if 'user_id' in session:
if session['role'] == 'admin':
return redirect(url_for('admin_main'))
else:
return redirect(url_for('user_main'))
return redirect(url_for('login'))
# 登录路由处理
@app.route('/login', methods=['GET', 'POST'])
def login():
"""
登录处理函数
GET: 返回登录页面
POST: 处理登录请求
"""
if request.method == 'GET':
return render_template('login.html')
try:
# 获取POST请求中的JSON数据
data = request.get_json()
if not data:
return jsonify({'status': 'error', 'message': '无效的请求数据'}), 400
username = data.get('username')
password = data.get('password')
if not username or not password:
return jsonify({'status': 'error', 'message': '用户名和密码不能为空'}), 400
# 连接数据库验证用户信息
conn = get_db()
try:
with conn.cursor() as cursor:
# 使用参数化查询防止SQL注入
sql = "SELECT * FROM users WHERE username = %s AND password = %s"
cursor.execute(sql, (username, password))
user = cursor.fetchone()
if user:
# 登录成功设置session
session['user_id'] = user[0] # 用户ID
session['role'] = user[6] # 用户角色
return jsonify({'status': 'success', 'role': user[6]})
return jsonify({'status': 'error', 'message': '用户名或密码错误'}), 401
finally:
conn.close()
except Exception as e:
print(f"Login error: {e}")
return jsonify({'status': 'error', 'message': '服务器内部错误'}), 500
# 注册页面路由
@app.route('/register', methods=['GET'])
def register_page():
"""
渲染注册页面
"""
return render_template('register.html')
# 处理注册请求的API
@app.route('/api/register', methods=['POST'])
def register_user():
"""
处理用户注册请求
将新用户添加到数据库
"""
data = request.get_json()
username = data.get('username')
password = data.get('password')
name = data.get('name')
gender = data.get('gender')
unit = data.get('unit')
role = data.get('role', 'user') # 默认角色为user
# 简单的数据验证
if not all([username, password, name, gender, unit, role]):
return jsonify({'status': 'error', 'message': '所有字段均为必填项'}), 400
conn = get_db()
try:
with conn.cursor() as cursor:
# 检查用户名是否已存在
sql_check_user = "SELECT id FROM users WHERE username = %s"
cursor.execute(sql_check_user, (username,))
existing_user = cursor.fetchone()
if existing_user:
return jsonify({'status': 'error', 'message': f'用户名 "{username}" 已存在'}), 409 # 409 Conflict
# 插入新用户到 users 表
# 注意:这里直接存储明文密码,生产环境中应使用密码哈希函数
sql_insert_user = """INSERT INTO users (username, password, name, gender, unit, role)
VALUES (%s, %s, %s, %s, %s, %s)"""
cursor.execute(sql_insert_user, (username, password, name, gender, unit, role))
conn.commit()
return jsonify({'status': 'success', 'message': '用户注册成功'}), 201 # 201 Created
except pymysql.Error as e:
conn.rollback()
print(f"Database error during registration: {e}")
return jsonify({'status': 'error', 'message': f'数据库错误: {e}'}), 500
except Exception as e:
conn.rollback()
print(f"An unexpected error occurred during registration: {e}")
return jsonify({'status': 'error', 'message': f'服务器内部错误: {e}'}), 500
finally:
conn.close()
# 管理员主页路由
@app.route('/admin_main')
@login_required
def admin_main():
"""
管理员主页处理函数
验证用户是否为管理员
"""
if session['role'] != 'admin':
return redirect(url_for('user_main'))
return render_template('admin_main.html')
# 新增党费记录页面路由
@app.route('/add_record')
@login_required
def add_record_page():
"""
渲染新增党费记录页面
"""
if session['role'] != 'admin':
return jsonify({'status': 'error', 'message': '权限不足'}), 403 # 返回403 Forbidden
return render_template('add.html')
# 编辑党费记录页面路由
@app.route('/revise')
@login_required
def revise_page():
"""
渲染新增党费记录页面
"""
if session['role'] != 'admin':
return jsonify({'status': 'error', 'message': '权限不足'}), 403 # 返回403 Forbidden
return render_template('revise.html')
# 用户主页路由
@app.route('/user_main')
@login_required #登陆验证
def user_main():
"""
普通用户主页处理函数
"""
return render_template('user_main.html')
# 用户详情页路由
@app.route('/user_detail')
@login_required #登陆验证
def user_detail():
"""
普通用户详情页处理函数
"""
return render_template('user_detail.html')
# 获取用户信息的API
@app.route('/api/user', methods=['GET'])
@login_required
def get_user_info():
"""
获取当前登录用户信息的API
返回用户详细信息
"""
if 'user_id' not in session:
return jsonify({'error': '未登录'}), 401
try:
conn = get_db()
with conn.cursor(pymysql.cursors.DictCursor) as cursor:
sql = "SELECT * FROM users WHERE id = %s"
cursor.execute(sql, (session['user_id'],))
user = cursor.fetchone()
if not user:
return jsonify({'error': '用户不存在'}), 404
return jsonify(user)
finally:
if conn:
conn.close()
# 获取党费记录列表的API
@app.route('/api/dues', methods=['GET'])
@login_required
def get_dues():
"""
获取党费记录列表的API
管理员可查看所有记录普通用户只能查看自己的记录
支持按日期范围筛选
"""
conn = get_db()
try:
start_date = request.args.get('start')
end_date = request.args.get('end')
with conn.cursor(pymysql.cursors.DictCursor) as cursor:
if session['role'] == 'admin':
base_sql = "SELECT d.*, u.name, u.unit, u.gender FROM dues d JOIN users u ON d.user_id = u.id"
params = []
if start_date and end_date:
base_sql += " WHERE payment_date BETWEEN %s AND %s"
params = [start_date, end_date]
cursor.execute(base_sql, params)
else:
base_sql = "SELECT d.*, u.name, u.unit, u.gender FROM dues d JOIN users u ON d.user_id = u.id WHERE d.user_id = %s"
params = [session['user_id']]
if start_date and end_date:
base_sql += " AND payment_date BETWEEN %s AND %s"
params.extend([start_date, end_date])
cursor.execute(base_sql, params)
dues = cursor.fetchall()
# 转换 Decimal 类型为 float使其可 JSON 序列化
for row in dues:
for key, value in row.items():
if isinstance(value, decimal.Decimal): # 检查是否是Decimal类型
row[key] = float(value) # 转换为float
return jsonify(dues)
except Exception as e:
print(f"Error fetching dues: {e}")
# 返回一个包含错误信息的 JSON 响应,并附带适当的状态码
return jsonify({'status': 'error', 'message': '获取党费记录失败'}), 500
finally:
conn.close()
# 添加党费记录的API
@app.route('/api/dues', methods=['POST'])
@login_required
def add_due():
"""
添加党费记录的API
仅管理员可操作
"""
if session['role'] != 'admin':
return jsonify({'status': 'error', 'message': '权限不足'}), 403 # 返回403 Forbidden
data = request.get_json()
user_id = data.get('userId')
due_amount = data.get('dueAmount')
actual_amount = data.get('actualAmount')
payment_type = data.get('paymentType')
payment_date = data.get('paymentDate')
remark = data.get('remark', '')
# 验证必填字段
if not all([user_id, due_amount, actual_amount, payment_type, payment_date is not None]): # 确保payment_date不为None
return jsonify({'status': 'error', 'message': '缺少必填字段'}), 400 # 返回400 Bad Request
# 验证金额是有效的数字
try:
due_amount = float(due_amount)
actual_amount = float(actual_amount)
if due_amount < 0 or actual_amount < 0:
return jsonify({'status': 'error', 'message': '金额不能为负数'}), 400
except ValueError:
return jsonify({'status': 'error', 'message': '金额格式不正确'}), 400
# 验证payment_date格式 (可选,取决于前端发送的格式)
try:
datetime.strptime(payment_date, '%Y-%m-%d') # 假设前端发送的是'YYYY-MM-DD'格式
except ValueError:
return jsonify({'status': 'error', 'message': '日期格式不正确应为YYYY-MM-DD'}), 400
conn = get_db()
try:
with conn.cursor() as cursor:
# 1. 验证用户是否存在
sql_check_user = "SELECT id FROM users WHERE id = %s"
cursor.execute(sql_check_user, (user_id,))
user_exists = cursor.fetchone()
if not user_exists:
return jsonify({'status': 'error', 'message': f'用户ID {user_id} 不存在'}), 404 # 返回404 Not Found
# 2. 计算状态
status = '已交齐' if actual_amount >= due_amount else '未交齐'
# 3. 插入党费记录
sql_insert_due = """INSERT INTO dues (user_id, due_amount, actual_amount, payment_type,
payment_date, status, remark) VALUES (%s, %s, %s, %s, %s, %s, %s)"""
cursor.execute(sql_insert_due, (
user_id,
due_amount,
actual_amount,
payment_type,
payment_date,
status,
remark
))
conn.commit()
return jsonify({'status': 'success', 'message': '党费记录添加成功'}), 201 # 返回201 Created
except pymysql.Error as e:
conn.rollback() # 发生错误回滚事务
print(f"Database error: {e}")
return jsonify({'status': 'error', 'message': f'数据库错误: {e}'}), 500 # 返回500 Internal Server Error
except Exception as e:
conn.rollback()
print(f"An unexpected error occurred: {e}")
return jsonify({'status': 'error', 'message': f'服务器内部错误: {e}'}), 500
finally:
conn.close()
#删除党费记录的API
@app.route('/api/dues/<int:due_id>', methods=['DELETE'])
@login_required
def delete_due(due_id):
"""
修改党费记录的API
仅管理员可操作
"""
if session['role'] != 'admin':
return jsonify({'status': 'error', 'message': '权限不足'})
data = request.get_json()
conn = get_db()
try:
with conn.cursor() as cursor:
sql = """DELETE FROM dues WHERE id = %s"""
cursor.execute(sql, (due_id))
conn.commit()
return jsonify({'status': 'success'})
finally:
conn.close()
# 修改党费记录的API
@app.route('/api/dues/<int:due_id>', methods=['PUT'])
@login_required
def update_due(due_id):
"""
修改党费记录的API
仅管理员可操作
"""
if session['role'] != 'admin':
return jsonify({'status': 'error', 'message': '权限不足'})
data = request.get_json()
conn = get_db()
try:
with conn.cursor() as cursor:
sql = """UPDATE dues SET due_amount = %s, actual_amount = %s,
payment_type = %s, payment_date = %s, status = %s, remark = %s
WHERE id = %s"""
cursor.execute(sql, (
data['dueAmount'],
data['actualAmount'],
data['paymentType'],
data['paymentDate'],
'已交齐' if data['actualAmount'] >= data['dueAmount'] else '未交齐',
data.get('remark', ''),
due_id
))
conn.commit()
return jsonify({'status': 'success'})
finally:
conn.close()
# 获取统计数据的API
@app.route('/api/stats', methods=['GET'])
@login_required
def get_stats():
"""
获取统计数据的API
仅管理员可访问
"""
if session['role'] != 'admin':
return jsonify({'status': 'error', 'message': '权限不足'}), 403
start_date = request.args.get('start')
end_date = request.args.get('end')
conn = get_db()
try:
with conn.cursor(pymysql.cursors.DictCursor) as cursor:
# 基础统计
sql = """
SELECT
COUNT(*) as total_count,
SUM(actual_amount) as total_amount,
AVG(actual_amount) as avg_amount,
COUNT(CASE WHEN status = '已交齐' THEN 1 END) as paid_count,
COUNT(CASE WHEN status = '未交齐' THEN 1 END) as unpaid_count
FROM dues
WHERE payment_date BETWEEN %s AND %s
"""
cursor.execute(sql, (start_date, end_date))
basic_stats = cursor.fetchone()
# 按单位统计
sql = """
SELECT
u.unit,
COUNT(*) as count,
SUM(d.actual_amount) as total_amount,
COUNT(CASE WHEN d.status = '已交齐' THEN 1 END) as paid_count
FROM dues d
JOIN users u ON d.user_id = u.id
WHERE d.payment_date BETWEEN %s AND %s
GROUP BY u.unit
"""
cursor.execute(sql, (start_date, end_date))
unit_stats = cursor.fetchall()
# 按月份统计
sql = """
SELECT
DATE_FORMAT(payment_date, '%%Y-%%m') as month,
COUNT(*) as count,
SUM(actual_amount) as total_amount
FROM dues
WHERE payment_date BETWEEN %s AND %s
GROUP BY DATE_FORMAT(payment_date, '%%Y-%%m')
ORDER BY month
"""
cursor.execute(sql, (start_date, end_date))
monthly_stats = cursor.fetchall()
# 转换 Decimal 类型为 float
for key, value in basic_stats.items():
if isinstance(value, decimal.Decimal):
basic_stats[key] = float(value)
for stat in unit_stats:
for key, value in stat.items():
if isinstance(value, decimal.Decimal):
stat[key] = float(value)
for stat in monthly_stats:
for key, value in stat.items():
if isinstance(value, decimal.Decimal):
stat[key] = float(value)
return jsonify({
'basic': basic_stats,
'by_unit': unit_stats,
'by_month': monthly_stats
})
except Exception as e:
print(f"Error fetching stats: {e}")
return jsonify({'status': 'error', 'message': '获取统计数据失败'}), 500
finally:
conn.close()
# 获取未缴纳人员列表的API
@app.route('/api/unpaid', methods=['GET'])
@login_required
def get_unpaid():
"""
获取未缴纳人员列表的API
仅管理员可访问
"""
if session['role'] != 'admin':
return jsonify({'status': 'error', 'message': '权限不足'})
conn = get_db()
try:
with conn.cursor(pymysql.cursors.DictCursor) as cursor:
sql = """SELECT u.* FROM users u
LEFT JOIN dues d ON u.id = d.user_id
WHERE d.id IS NULL OR d.status = '未交齐'"""
cursor.execute(sql)
users = cursor.fetchall()
return jsonify({'count': len(users), 'users': users})
finally:
conn.close()
# 导入数据的API
@app.route('/api/import', methods=['POST'])
@login_required
def import_data():
"""
批量导入党费数据的API
仅管理员可操作
通过姓名匹配用户ID
"""
if session['role'] != 'admin':
return jsonify({'status': 'error', 'message': '权限不足'})
data = request.get_json()
if not data or not isinstance(data, list):
return jsonify({'status': 'error', 'message': '无效的导入数据格式'})
conn = get_db()
try:
with conn.cursor() as cursor:
# 创建用户姓名到ID的映射
cursor.execute("SELECT id, name FROM users")
user_map = {row[1]: row[0] for row in cursor.fetchall()}
success_count = 0
failed_records = []
for idx, item in enumerate(data):
try:
# 验证必要字段
if not all(k in item for k in ['name', 'date', 'due', 'actual']):
failed_records.append(f"{idx+1}行: 缺少必要字段")
continue
# 获取用户ID
user_id = user_map.get(item['name'])
if not user_id:
failed_records.append(f"{idx+1}行: 用户'{item['name']}'不存在")
continue
# 验证数据格式
due_amount = float(item['due'])
actual_amount = float(item['actual'])
if due_amount < 0 or actual_amount < 0:
failed_records.append(f"{idx+1}行: 金额不能为负数")
continue
# 验证日期格式
try:
datetime.strptime(item['date'], '%Y-%m-%d')
except ValueError:
failed_records.append(f"{idx+1}行: 日期格式不正确应为YYYY-MM-DD")
continue
# 插入记录
status = '已交齐' if actual_amount >= due_amount else '未交齐'
sql = """INSERT INTO dues (user_id, due_amount, actual_amount,
payment_type, payment_date, status)
VALUES (%s, %s, %s, 'monthly', %s, %s)"""
cursor.execute(sql, (
user_id,
due_amount,
actual_amount,
item['date'],
status
))
success_count += 1
except Exception as e:
failed_records.append(f"{idx+1}行: 处理失败 - {str(e)}")
conn.commit()
result = {
'status': 'success',
'success_count': success_count,
'failed_count': len(failed_records)
}
if failed_records:
result['failed_details'] = failed_records
return jsonify(result)
finally:
conn.close()
# 退出登录路由
@app.route('/logout')
def logout():
"""
退出登录处理函数
清除session数据并重定向到登录页面
"""
session.clear()
return redirect(url_for('login'))
# 统计分析页面路由
@app.route('/stats')
@login_required
def stats_page():
if session['role'] != 'admin':
return jsonify({'status': 'error', 'message': '权限不足'}), 403
return render_template('stats.html')
# 数据导入/导出页面路由
@app.route('/data_handle')
@login_required
def data_handle_page():
if session['role'] != 'admin':
return jsonify({'status': 'error', 'message': '权限不足'}), 403
return render_template('data_handle.html')
# 获取单条党费记录的API
@app.route('/api/dues/<int:due_id>', methods=['GET'])
@login_required
def get_due(due_id):
"""
获取单条党费记录的API
管理员可查看所有记录普通用户只能查看自己的记录
"""
conn = get_db()
try:
with conn.cursor(pymysql.cursors.DictCursor) as cursor:
if session['role'] == 'admin':
sql = """SELECT d.*, u.name, u.unit, u.gender
FROM dues d
JOIN users u ON d.user_id = u.id
WHERE d.id = %s"""
cursor.execute(sql, (due_id,))
else:
sql = """SELECT d.*, u.name, u.unit, u.gender
FROM dues d
JOIN users u ON d.user_id = u.id
WHERE d.id = %s AND d.user_id = %s"""
cursor.execute(sql, (due_id, session['user_id']))
due = cursor.fetchone()
if not due:
return jsonify({'status': 'error', 'message': '记录不存在'}), 404
# 转换 Decimal 类型为 float
for key, value in due.items():
if isinstance(value, decimal.Decimal):
due[key] = float(value)
return jsonify(due)
except Exception as e:
print(f"Error fetching due record: {e}")
return jsonify({'status': 'error', 'message': '获取记录失败'}), 500
finally:
conn.close()
# 主程序入口
if __name__ == '__main__':
# 启动Flask应用开启调试模式
app.run(debug=True)

@ -0,0 +1,165 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>新增党费记录</title>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
<style>
.form-container {
max-width: 800px;
margin: 2rem auto;
padding: 2rem;
border: 1px solid #dee2e6;
border-radius: 8px;
}
.required-label::after {
content: "*";
color: red;
margin-left: 4px;
}
</style>
</head>
<body>
<div class="container">
<div class="form-container">
<h2 class="mb-4">新增党费记录</h2>
<!-- 新增记录表单 -->
<form id="addForm">
<!-- 姓名 -->
<div class="mb-3">
<label class="form-label required-label">姓名</label>
<input type="text" class="form-control" id="name" required>
</div>
<!-- ID -->
<div class="mb-3">
<label class="form-label required-label">ID</label>
<input type="text" class="form-control" id="userId" required>
</div>
<!-- 性别 -->
<div class="mb-3">
<label class="form-label required-label">性别</label>
<div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" id="male" value="男" required>
<label class="form-check-label" for="male"></label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" id="female" value="女">
<label class="form-check-label" for="female"></label>
</div>
</div>
</div>
<!-- 单位 -->
<div class="mb-3">
<label class="form-label required-label">单位</label>
<input type="text" class="form-control" id="unit" required>
</div>
<!-- 金额组 -->
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label required-label">应缴金额(元)</label>
<input type="number" class="form-control" id="dueAmount" min="0" step="0.01" required>
</div>
<div class="col-md-6">
<label class="form-label required-label">实缴金额(元)</label>
<input type="number" class="form-control" id="actualAmount" min="0" step="0.01" required>
</div>
</div>
<!-- 缴费类型 -->
<div class="mb-3">
<label class="form-label required-label">缴费类型</label>
<select class="form-select" id="paymentType" required>
<option value="">请选择</option>
<option value="monthly">月度缴费</option>
<option value="special">特殊党费</option>
<option value="other">其他</option>
</select>
</div>
<!-- 缴费日期 -->
<div class="mb-3">
<label class="form-label required-label">缴费日期</label>
<input type="date" class="form-control" id="paymentDate" required>
</div>
<!-- 备注 -->
<div class="mb-4">
<label class="form-label">备注</label>
<textarea class="form-control" id="remark" rows="3"></textarea>
</div>
<!-- 操作按钮 -->
<div class="d-flex justify-content-end gap-2">
<button type="button" class="btn btn-secondary" onclick="goBack()">取消</button>
<button type="submit" class="btn btn-primary">提交</button>
</div>
</form>
</div>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
<script>
// 返回管理主页
function goBack() {
window.location.href = "{{ url_for('admin_main') }}";
}
// 表单提交处理
$('#addForm').submit(function(e) {
e.preventDefault();
// 表单验证
const due = parseFloat($('#dueAmount').val());
const actual = parseFloat($('#actualAmount').val());
if (actual > due) {
alert('实缴金额不能超过应缴金额');
return;
}
// 自动计算状态
const status = actual >= due ? '已交齐' : '未交齐';
// 构建数据对象
const formData = {
userId: $('#userId').val(),
dueAmount: due,
actualAmount: actual,
paymentType: $('#paymentType').val(),
paymentDate: $('#paymentDate').val(),
remark: $('#remark').val(),
status: status
};
// 发送请求
$.ajax({
url: '/api/dues',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(formData),
success: function() {
alert('提交成功!');
goBack();
},
error: function(xhr) {
alert(`提交失败:${xhr.responseJSON?.message || '服务器错误'}`);
}
});
});
// 输入验证
$('#dueAmount, #actualAmount').on('input', function() {
const value = parseFloat($(this).val());
if (value < 0) {
$(this).val(0);
}
});
</script>
</body>
</html>

@ -0,0 +1,264 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<!-- 基础元信息 -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>党费管理系统 - 管理后台</title>
<!-- 引入Bootstrap CSS框架 -->
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
<!-- 引入Font Awesome图标库 -->
<link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<!-- 自定义样式 -->
<style>
/* 状态显示样式 */
.status-paid { color: #28a745; } /* 已交齐状态绿色 */
.status-unpaid { color: #dc3545; } /* 未交齐状态红色 */
.operation-btns { margin: 20px 0; gap: 10px; } /* 操作按钮容器边距+间隔 */
.search-box { max-width: 300px; } /* 搜索框宽度限制 */
</style>
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-dark bg-primary">
<div class="container-fluid">
<!-- 系统Logo -->
<a class="navbar-brand" href="#">
<i class="fas fa-home me-2"></i>党费管理系统
</a>
<!-- 功能导航区 -->
<div class="d-flex">
<!-- 统计分析按钮 -->
<button class="btn btn-outline-light me-2" onclick="window.location.href='{{ url_for('stats_page') }}'">
<i class="fas fa-chart-bar me-1"></i>统计分析
</button>
<!-- 导入导出按钮 -->
<button class="btn btn-outline-light me-2" onclick="window.location.href='{{ url_for('data_handle_page') }}'">
<i class="fas fa-file-import me-1"></i>数据导入/导出
</button>
<!-- 退出登录按钮 -->
<button class="btn btn-outline-light" onclick="logout()">
<i class="fas fa-sign-out-alt me-1"></i>退出
</button>
</div>
</div>
</nav>
<!-- 数据管理区 -->
<div class="container mt-4">
<!-- 操作按钮组 -->
<div class="operation-btns d-flex justify-content-between align-items-center">
<div>
<button class="btn btn-primary me-2" style="box-shadow: 0 2px 4px rgba(0,0,0,0.1);" onclick="window.location.href='{{ url_for('add_record_page') }}'">
<i class="fas fa-plus me-1"></i>新增记录
</button>
<button class="btn btn-danger me-2" style="box-shadow: 0 2px 4px rgba(0,0,0,0.1);" onclick="deleteSelected()">
<i class="fas fa-trash me-1"></i>删除记录
</button>
<button class="btn btn-warning" onclick="editSelected()">
<i class="fas fa-edit me-1"></i>修改记录
</button>
</div>
<!-- 搜索框 -->
<div class="search-box">
<div class="input-group">
<input type="text" id="searchInput" class="form-control" placeholder="输入姓名或ID">
<button class="btn btn-primary" onclick="searchRecords()">
<i class="fas fa-search"></i>
</button>
</div>
</div>
</div>
<!-- 数据表格 -->
<div class="table-responsive">
<table class="table table-hover">
<thead class="table-dark">
<tr>
<th><input type="checkbox" id="selectAll"></th>
<th>姓名</th>
<th>ID</th>
<th>性别</th>
<th>单位</th>
<th>应缴金额</th>
<th>实缴金额</th>
<th>缴费日期</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody id="dataBody">
<!-- 数据通过JavaScript动态加载 -->
</tbody>
</table>
</div>
</div>
<!-- 引入jQuery库 -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
<script>
// 初始化数据存储
let currentData = [];
// 数据加载函数
// function loadData() {
// $.get('/api/dues', data => {
// currentData = data;
// renderTable(data);
// }).fail(() => {
// alert('加载数据失败,请刷新页面重试');
// });
// }
function loadData() {
$.ajax({
url: '/api/dues',
method: 'GET',
success: function(data){
currentData = data;
renderTable(data);
},
error :function(xhr, status, error){
alert('加载数据失败:' + error + '。请刷新页面重试');
}
})
}
// 表格渲染函数
function renderTable(data) {
const tbody = $('#dataBody').empty();//表格体
data.forEach(item => {
// 动态计算状态
const status = item.actual_amount >= item.due_amount ?
'<span class="status-paid">已交齐</span>' :
'<span class="status-unpaid">未交齐</span>';
// 构建表格行
tbody.append(`
<tr data-id="${item.id}">
<td><input type="checkbox" class="rowCheck"></td>
<td>${item.name}</td>
<td>${item.user_id}</td>
<td>${item.gender || '-'}</td>
<td>${item.unit}</td>
<td>¥${item.due_amount.toFixed(2)}</td>
<td>¥${item.actual_amount.toFixed(2)}</td>
<td>${new Date(item.payment_date).toLocaleDateString()}</td>
<td>${status}</td>
<td>
<button class="btn btn-sm btn-info me-1" onclick="viewDetail('${item.user_id}')">
<i class="fas fa-eye"></i>
</button>
<button class="btn btn-sm btn-warning me-1" onclick="editRecord('${item.id}')">
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-sm btn-danger" onclick="deleteRecord('${item.id}')">
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
`);
});
}
// 全选/取消全选
$('#selectAll').change(function() {
$('.rowCheck').prop('checked', this.checked);
});
// 查看详情
function viewDetail(id) {
window.location.href = `{{ url_for('user_detail') }}?id=${id}`;
}
// 编辑记录
function editRecord(id) {
window.location.href = `{{ url_for('revise_page') }}?id=${id}`;
}
// 删除记录
function deleteRecord(id) {
//console.log(id);
if (confirm('确定要删除这条记录吗?')) {
$.ajax({
url: `/api/dues/${id}`,
method: 'DELETE',
success: () => {
alert('删除成功');
loadData();
},
error: () => alert('删除失败')
});
}
}
// 删除选中记录
function deleteSelected() {
const selected = $('.rowCheck:checked').length;
if (selected === 0) {
alert('请选择要删除的记录');
return;
}
if (confirm(`确定要删除选中的${selected}条记录吗?`)) {
const ids = [];
$('.rowCheck:checked').each(function() {
ids.push($(this).closest('tr').data('id'));
});
// 批量删除
Promise.all(ids.map(id =>
$.ajax({
url: `/api/dues/${id}`,
method: 'DELETE'
})
)).then(() => {
alert('删除成功');
loadData();
}).catch(() => alert('删除失败'));
}
}
// 编辑选中记录
function editSelected() {
const selected = $('.rowCheck:checked');
if (selected.length !== 1) {
alert('请选择一条记录进行编辑');
return;
}
const id = selected.closest('tr').data('id');
editRecord(id);
}
// 搜索记录
function searchRecords() {
const keyword = $('#searchInput').val().toLowerCase();
if (!keyword) {
renderTable(currentData);
return;
}
const filtered = currentData.filter(item =>
item.name.toLowerCase().includes(keyword) ||
item.user_id.toString().includes(keyword)
);
renderTable(filtered);
}
// 退出登录
function logout() {
if (confirm('确定要退出登录吗?')) {
window.location.href = '/logout';
}
}
// 页面加载时初始化数据
$(document).ready(() => loadData());
</script>
</body>
</html>

@ -0,0 +1,174 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>党费管理系统 - 仪表板</title>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
.sidebar { /* 调整为与导航栏协调的深蓝色 */
min-height: 100vh;
background-color: #0d6efd; /* 与导航栏同色 */
padding-top: 20px;
}
.sidebar .nav-link {
color: #fff;
padding: 10px 20px;
}
.sidebar .nav-link:hover {
background-color: #495057;
}
.sidebar .nav-link.active {
background-color: #0d6efd;
}
.main-content {
padding: 20px;
}
.stat-card { /* 优化卡片样式 */
border-radius: 12px; /* 更大圆角 */
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* 更明显阴影 */
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="row">
<!-- 侧边栏 -->
<div class="col-md-2 sidebar">
<h3 class="text-white text-center mb-4">党费管理系统</h3>
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link active" href="#">
<i class="fas fa-home me-2"></i>首页
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<i class="fas fa-users me-2"></i>学生管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<i class="fas fa-money-bill me-2"></i>党费管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<i class="fas fa-cog me-2"></i>系统设置
</a>
</li>
<li class="nav-item mt-5">
<a class="nav-link" href="#" id="logoutBtn">
<i class="fas fa-sign-out-alt me-2"></i>退出登录
</a>
</li>
</ul>
</div>
<!-- 主要内容区域 -->
<div class="col-md-10 main-content">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>仪表板</h2>
<div class="user-info">
<span id="username"></span>
<span class="ms-2">|</span>
<span id="role" class="ms-2"></span>
</div>
</div>
<!-- 统计卡片 -->
<div class="row">
<div class="col-md-3">
<div class="stat-card bg-primary text-white">
<h3>总学生数</h3>
<h2 id="totalStudents">0</h2>
</div>
</div>
<div class="col-md-3">
<div class="stat-card bg-success text-white">
<h3>本月缴费</h3>
<h2 id="monthlyPayment">¥0</h2>
</div>
</div>
<div class="col-md-3">
<div class="stat-card bg-warning text-white">
<h3>待缴费</h3>
<h2 id="pendingPayment">¥0</h2>
</div>
</div>
<div class="col-md-3">
<div class="stat-card bg-info text-white">
<h3>已缴费</h3>
<h2 id="paidAmount">¥0</h2>
</div>
</div>
</div>
<!-- 最近活动 -->
<div class="card mt-4">
<div class="card-header">
<h5 class="mb-0">最近活动</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>时间</th>
<th>学生</th>
<th>活动</th>
<th>金额</th>
</tr>
</thead>
<tbody id="recentActivities">
<!-- 动态填充数据 -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>
<script>
$(document).ready(function() {
// 检查登录状态
const token = localStorage.getItem('token');
if (!token) {
window.location.href = '/login';
return;
}
// 获取用户信息
$.ajax({
url: '/api/user',
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`
},
success: function(response) {
$('#username').text(response.username);
$('#role').text(response.role === 'admin' ? '管理员' : '普通用户');
},
error: function() {
localStorage.removeItem('token');
window.location.href = '/login';
}
});
// 退出登录
$('#logoutBtn').click(function(e) {
e.preventDefault();
localStorage.removeItem('token');
window.location.href = '/login';
});
});
</script>
</body>
</html>

@ -0,0 +1,252 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>数据导入导出</title>
<!-- 引入Bootstrap样式表 -->
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
<style>
/* 定义操作区样式 */
.section-box {
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 20px;
margin-bottom: 30px;
}
/* 定义表格滚动容器 */
.preview-table {
max-height: 400px;
overflow-y: auto;
}
/* 定义数据错误高亮样式 */
.highlight {
background-color: #fff3cd !important;
}
</style>
</head>
<body>
<div class="container py-4">
<!-- 导出操作区 -->
<div class="section-box">
<h4 class="mb-4">数据导出</h4>
<!-- 时间选择组件 -->
<div class="row g-3 mb-4">
<div class="col-md-3">
<label>开始日期</label>
<input type="date" id="exportStart" class="form-control">
</div>
<div class="col-md-3">
<label>结束日期</label>
<input type="date" id="exportEnd" class="form-control">
</div>
<div class="col-md-2">
<!-- 查询按钮 -->
<button class="btn btn-primary mt-4" onclick="loadExportData()">查询</button>
</div>
</div>
<!-- 导出数据表格 -->
<div class="preview-table">
<table class="table table-hover">
<thead>
<tr>
<!-- 全选复选框 -->
<th>
<input type="checkbox" id="exportSelectAll">
</th>
<th>姓名</th>
<th>缴费日期</th>
<th>应缴金额</th>
<th>实缴金额</th>
<th>状态</th>
</tr>
</thead>
<tbody id="exportDataBody"></tbody>
</table>
</div>
<!-- 导出选中数据按钮 -->
<button class="btn btn-success mt-3" onclick="exportSelected()">
<i class="fas fa-download me-2"></i>导出选中数据
</button>
</div>
<!-- 导入操作区 -->
<div class="section-box">
<h4 class="mb-4">数据导入</h4>
<!-- CSV文件上传组件 -->
<div class="mb-4">
<input type="file" id="csvInput" class="form-control" accept=".csv">
<small class="text-muted">请选择CSV格式文件字段顺序姓名,缴费日期,应缴金额,实缴金额</small>
</div>
<!-- 导入数据预览表格 -->
<div class="preview-table">
<table class="table" id="importPreview">
<thead>
<tr>
<th>姓名</th>
<th>缴费日期</th>
<th>应缴金额</th>
<th>实缴金额</th>
<th>状态</th>
</tr>
</thead>
<tbody id="importDataBody"></tbody>
</table>
</div>
<!-- 确认导入按钮 -->
<button class="btn btn-primary mt-3" onclick="confirmImport()">
<i class="fas fa-upload me-2"></i>确认导入
</button>
</div>
</div>
<!-- 引入jQuery库 -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
<script>
// 定义导出数据存储数组
let exportData = [];
// 定义导入数据存储数组
let importData = [];
// 加载导出数据函数
function loadExportData() {
const start = $('#exportStart').val();
const end = $('#exportEnd').val();
// 发送AJAX请求获取数据
$.get(`/api/dues?start=${start}&end=${end}`, data => {
exportData = data;
renderExportTable(data);
});
}
// 渲染导出数据表格函数
function renderExportTable(data) {
const tbody = $('#exportDataBody').empty();
data.forEach(item => {
// 根据实缴金额判断状态
const status = item.actual >= item.due ?
'已交齐' : '未交齐';
// 动态生成表格行
tbody.append(`
<tr>
<td><input type="checkbox" class="export-check"></td>
<td>${item.name}</td>
<td>${item.payment_date}</td>
<td>${item.due_amount}</td>
<td>${item.actual_amount}</td>
<td>${status}</td>
</tr>
`);
});
}
// 全选功能实现
$('#exportSelectAll').change(function() {
$('.export-check').prop('checked', this.checked);
});
// 导出选中数据函数
function exportSelected() {
const selected = [];
$('.export-check:checked').each(function() {
const index = $(this).closest('tr').index();
selected.push(exportData[index]);
});
if(selected.length === 0) return alert('请选择要导出的记录');
// 生成CSV内容
const csvContent = [
['姓名', '缴费日期', '应缴金额', '实缴金额', '状态'].join(','),
...selected.map(item => [
`"${item.name}"`,
item.payment_date,
item.due_amount,
item.actual_amount,
item.actual >= item.due ? '已交齐' : '未交齐'
].join(','))
].join('\n');
// 创建下载链接
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `党费数据_${new Date().toISOString().slice(0,10)}.csv`;
link.click();
}
// CSV文件解析函数
$('#csvInput').change(function(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
const text = e.target.result;
importData = parseCSV(text);
renderImportPreview(importData);
};
reader.readAsText(file, 'UTF-8');
});
// CSV解析逻辑
function parseCSV(text) {
const rows = text.split('\n').filter(row => row.trim() !== '');
return rows.slice(1).map(row => {
const [name, date, due, actual] = row.split(',').map(f => f.replace(/"/g, ''));
return {
name: name.trim(),
date: date.trim(),
due: parseFloat(due),
actual: parseFloat(actual)
};
});
}
// 渲染导入预览表格
function renderImportPreview(data) {
const tbody = $('#importDataBody').empty();
data.forEach(item => {
const isValid = !isNaN(item.due) && !isNaN(item.actual);
tbody.append(`
<tr class="${isValid ? '' : 'table-danger'}">
<td>${item.name}</td>
<td>${item.date}</td>
<td>${item.due}</td>
<td>${item.actual}</td>
<td>${isValid ? '有效' : '数据错误'}</td>
</tr>
`);
});
}
// 确认导入函数
function confirmImport() {
const validData = importData.filter(item =>
!isNaN(item.due) && !isNaN(item.actual));
if(validData.length === 0) return alert('没有有效数据可导入');
// 发送导入请求
$.ajax({
url: '/api/import',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(validData),
success: () => {
alert(`成功导入${validData.length}条记录`);
$('#csvInput').val('');
$('#importDataBody').empty();
}
});
}
</script>
</body>
</html>

@ -0,0 +1,154 @@
<!DOCTYPE html>
<!-- 声明文档类型为HTML5 -->
<html lang="zh-CN">
<!-- 指定文档语言为中文 -->
<head>
<!-- 基础元信息设置 -->
<meta charset="UTF-8">
<!-- 设置字符编码为UTF-8 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 响应式视口设置 -->
<title>党费管理系统 - 登录</title>
<!-- 页面标题 -->
<!-- 引入Bootstrap 5 CSS框架 -->
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
<!-- 自定义样式 -->
<style>
/* 页面整体样式 */
body {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); /* 渐变背景 */
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
/* 登录容器样式 */
.login-container {
max-width: 400px;
margin: 100px auto;
padding: 20px;
border: 1px solid #dee2e6;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
/* 系统标题样式 */
.login-title {
text-align: center;
margin-bottom: 30px;
}
/* 表单标签样式 */
.form-label {
font-weight: 500; /* 中等字重标签 */
}
/* 登录按钮样式 */
.btn-primary {
width: 100%;
background-color: #0d6efd; /* 统一使用Bootstrap主蓝色 */
border: none;
padding: 12px;
color: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1); /* 添加轻微阴影 */
}
.btn-primary:hover {
background-color: #0b5ed7; /* 更柔和的悬停色 */
color: white;
transform: translateY(-1px); /* 悬停微抬效果 */
}
/* 错误消息样式 */
.error-msg {
color: #dc3545; /* 错误提示红色 */
font-size: 0.9em; /* 较小字号 */
margin-top: 5px; /* 顶部边距 */
display: none;
}
</style>
</head>
<body>
<div class="container">
<div class="login-container">
<h2 class="login-title">党费管理系统</h2>
<form id="loginForm">
<div class="mb-3">
<label for="username" class="form-label">用户名</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">密码</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">登录</button>
<button type="button" class="btn btn-secondary" onclick="window.location.href='{{ url_for('register_page') }}'">注册</button>
</div>
</form>
</div>
</div>
<!-- 引入jQuery库 -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
<script>
// 页面加载完成后执行
$(document).ready(function() {
// 表单提交处理
$('#loginForm').on('submit', function(e) {
e.preventDefault();
// 清除之前的错误信息
$('.error-msg').hide();
// 表单验证
let isValid = true;
const username = $('#username').val().trim();
const password = $('#password').val().trim();
if (!username) {
$('#usernameError').text('请输入用户名').show();
isValid = false;
}
if (!password) {
$('#passwordError').text('请输入密码').show();
isValid = false;
}
if (!isValid) return;
// 发送登录请求
$.ajax({
url: '/login',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({
username: username,
password: password
}),
success: function(response) {
if (response.status === 'success') {
// 登录成功,根据角色跳转
if (response.role === 'admin') {
window.location.href = '/admin_main';
} else {
window.location.href = '/user_main';
}
} else {
$('#generalError').text(response.message).show();
}
},
error: function(xhr) {
$('#generalError').text('登录失败,请稍后重试').show();
}
});
});
// 输入框获得焦点时清除错误信息
$('input').on('focus', function() {
$(this).siblings('.error-msg').hide();
$('#generalError').hide();
});
});
</script>
</body>
</html>

@ -0,0 +1,183 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户注册 - 党费管理系统</title>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); /* 与登录页统一渐变背景 */
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.register-container {
max-width: 500px;
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.system-title {
color: #c00;
text-align: center;
margin-bottom: 30px;
font-weight: bold;
}
.form-label {
font-weight: 500;
}
.register-btn {
width: 100%;
background-color: #0d6efd; /* 统一使用Bootstrap主蓝色 */
border: none;
padding: 12px;
color: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1); /* 添加阴影 */
}
.register-btn:hover {
background-color: #0b5ed7; /* 更柔和的悬停色 */
color: white;
transform: translateY(-1px); /* 悬停微抬效果 */
}
.error-msg {
color: #dc3545;
font-size: 0.9em;
margin-top: 5px;
display: none;
}
</style>
</head>
<body>
<div class="register-container">
<h2 class="system-title">用户注册</h2>
<form id="registerForm">
<div class="mb-3">
<label for="username" class="form-label">用户名</label>
<input type="text" class="form-control" id="username" name="username" required>
<div id="usernameError" class="error-msg"></div>
</div>
<div class="mb-3">
<label for="password" class="form-label">密码</label>
<input type="password" class="form-control" id="password" name="password" required>
<div id="passwordError" class="error-msg"></div>
</div>
<div class="mb-3">
<label for="name" class="form-label">姓名</label>
<input type="text" class="form-control" id="name" name="name" required>
<div id="nameError" class="error-msg"></div>
</div>
<div class="mb-3">
<label class="form-label">性别</label>
<div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" id="male" value="男" required>
<label class="form-check-label" for="male"></label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" id="female" value="女">
<label class="form-check-label" for="female"></label>
</div>
</div>
<div id="genderError" class="error-msg"></div>
</div>
<div class="mb-3">
<label for="unit" class="form-label">单位</label>
<input type="text" class="form-control" id="unit" name="unit" required>
<div id="unitError" class="error-msg"></div>
</div>
<div class="mb-3">
<label for="role" class="form-label">角色</label>
<select class="form-select" id="role" name="role" required>
<option value="">请选择</option>
<option value="user">普通用户</option>
<option value="admin">管理员</option> {#-- 管理员角色通常应限制注册 --#}
</select>
<div id="roleError" class="error-msg"></div>
</div>
<div class="mb-3">
<label for="name" class="form-label">联系方式</label>
<input type="text" class="form-control" id="contact" name="contact" required>
<div id="contactError" class="error-msg"></div>
</div>
<div class="d-grid mb-3">
<button type="submit" class="btn register-btn">注册</button>
</div>
<div class="text-center">
<a href="{{ url_for('login') }}">已有账号?去登录</a>
</div>
<!-- 通用错误消息显示区域 -->
<div id="generalError" class="error-msg mt-3"></div>
</form>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
$(document).ready(function() {
$('#registerForm').on('submit', function(e) {//为submit事件绑定一个函数
e.preventDefault();//阻止submit事件的默认行为刷新或跳转页面
$('.error-msg').hide();
let isValid = true;//let声明块级变量isValid只在花括号内有效用于标记表单验证通过
const username = $('#username').val().trim();//读取id=username的输入框内的内容.trim删除字符串的无用字符如首尾的空格、换行符
const password = $('#password').val().trim();
const name = $('#name').val().trim();
const gender = $('input[name="gender"]:checked').val();
const unit = $('#unit').val().trim();
const role = $('#role').val();
const contact = $('#contact').val().trim();
//验证
if (!username) { $('#usernameError').text('请输入用户名').show(); isValid = false; }
if (!password) { $('#passwordError').text('请输入密码').show(); isValid = false; }
if (!name) { $('#nameError').text('请输入姓名').show(); isValid = false; }
if (!gender) { $('#genderError').text('请选择性别').show(); isValid = false; }
if (!unit) { $('#unitError').text('请输入单位').show(); isValid = false; }
if (!role) { $('#roleError').text('请选择角色').show(); isValid = false; }
if (!contact) { $('#contact').text('请输入联系方式').show; isValid = false; }
if (!isValid) return;//任何一项验证不通过,则终止表单提交
$.ajax({//jQuery库下面是连接配置
url: '/api/register',//后端接口的路径
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({
username: username,
password: password,
name: name,
gender: gender,
unit: unit,
role: role,
contact: contact
}),
success: function(response) {//response 是服务器返回的解析后的 JSON 数据。
if (response.status === 'success') {
alert('注册成功!请返回登录页面。');
window.location.href = "{{ url_for('login') }}"; // 注册成功后跳转到登录页
} else {
$('#generalError').text(response.message).show();
}
},
error: function(xhr) {//xhr是XMLHttpRequest 对象
const errorMsg = xhr.responseJSON?.message || '注册失败,请稍后重试';//.?操作符解释:如果 xhr.responseJSON 为 null 或 undefined例如服务器未返回 JSON直接访问 xhr.responseJSON.message 会报错,但 ?. 会返回 undefined 而不报错。
$('#generalError').text(errorMsg).show();
}
});
});
$('input, select').on('focus', function() {//选择所有input、select元素绑定focus事件用户点击或Tab到输入框时触发
$(this).siblings('.error-msg').hide();
$(this).parent().siblings('.error-msg').hide(); // For radio buttons
$('#generalError').hide();
});
});
</script>
</body>
</html>

@ -0,0 +1,201 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<!-- 基础元信息 -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>修改党费记录</title>
<!-- 引入Bootstrap样式 -->
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
<!-- 自定义样式 -->
<style>
.form-container {
max-width: 800px; /* 限制表单最大宽度 */
margin: 2rem auto; /* 上下边距2rem水平居中 */
padding: 2rem; /* 内边距 */
border: 1px solid #dee2e6; /* 边框样式 */
border-radius: 8px; /* 圆角 */
}
.required-label::after { /* 必填项红星样式 */
content: "*";
color: red;
margin-left: 4px;
}
</style>
</head>
<body>
<div class="container">
<!-- 表单容器 -->
<div class="form-container">
<h2 class="mb-4">修改党费记录</h2>
<!-- 修改记录表单 -->
<form id="editForm">
<!-- 隐藏域存储原始ID -->
<input type="hidden" id="originalId">
<!-- 姓名输入 -->
<div class="mb-3">
<label class="form-label required-label">姓名</label>
<input type="text" class="form-control" id="name" required>
</div>
<!-- ID输入 -->
<div class="mb-3">
<label class="form-label required-label">ID</label>
<input type="text" class="form-control" id="userId" required>
</div>
<!-- 性别单选 -->
<div class="mb-3">
<label class="form-label required-label">性别</label>
<div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" id="male" value="男" required>
<label class="form-check-label" for="male"></label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" id="female" value="女">
<label class="form-check-label" for="female"></label>
</div>
</div>
</div>
<!-- 单位输入 -->
<div class="mb-3">
<label class="form-label required-label">单位</label>
<input type="text" class="form-control" id="unit" required>
</div>
<!-- 金额输入组 -->
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label required-label">应缴金额(元)</label>
<input type="number" class="form-control" id="dueAmount" min="0" step="0.01" required>
</div>
<div class="col-md-6">
<label class="form-label required-label">实缴金额(元)</label>
<input type="number" class="form-control" id="actualAmount" min="0" step="0.01" required>
</div>
</div>
<!-- 缴费类型下拉 -->
<div class="mb-3">
<label class="form-label required-label">缴费类型</label>
<select class="form-select" id="paymentType" required>
<option value="">请选择</option>
<option value="monthly">月度缴费</option>
<option value="special">特殊党费</option>
<option value="other">其他</option>
</select>
</div>
<!-- 缴费日期选择 -->
<div class="mb-3">
<label class="form-label required-label">缴费日期</label>
<input type="date" class="form-control" id="paymentDate" required>
</div>
<!-- 备注文本域 -->
<div class="mb-4">
<label class="form-label">备注</label>
<textarea class="form-control" id="remark" rows="3"></textarea>
</div>
<!-- 操作按钮组 -->
<div class="d-flex justify-content-end gap-2">
<button type="button" class="btn btn-secondary" onclick="goBack()">取消</button>
<button type="submit" class="btn btn-primary">提交</button>
</div>
</form>
</div>
</div>
<!-- 引入jQuery -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
<script>
// 页面加载完成后执行
$(document).ready(function() {
// 从URL参数获取记录ID
const urlParams = new URLSearchParams(window.location.search);
const recordId = urlParams.get('id');
if (!recordId) { // 如果缺少ID参数
alert('无效的记录ID');
goBack();
return;
}
// 加载原始记录数据
$.get(`/api/dues/${recordId}`)
.done(data => {
// 填充表单数据
$('#originalId').val(data.id); // 存储原始ID
$('#name').val(data.name);
$('#userId').val(data.user_id);
$(`input[name='gender'][value='${data.gender}']`).prop('checked', true);
$('#unit').val(data.unit);
$('#dueAmount').val(data.due_amount);
$('#actualAmount').val(data.actual_amount);
$('#paymentType').val(data.payment_type);
$('#paymentDate').val(data.payment_date.split('T')[0]); // 转换日期格式
$('#remark').val(data.remark);
})
.fail(() => {
alert('加载记录失败');
goBack();
});
});
// 返回管理主页
function goBack() {
window.location.href = "/admin_main";
}
// 表单提交处理
$('#editForm').submit(function(e) {
e.preventDefault(); // 阻止默认提交行为
// 获取表单数据
const formData = {
dueAmount: parseFloat($('#dueAmount').val()),
actualAmount: parseFloat($('#actualAmount').val()),
paymentType: $('#paymentType').val(),
paymentDate: $('#paymentDate').val(),
remark: $('#remark').val()
};
// 验证实缴金额
if (formData.actualAmount < 0) {
alert('实缴金额不能为负数');
return;
}
// 发送更新请求
$.ajax({
url: `/api/dues/${$('#originalId').val()}`,
method: 'PUT',
contentType: 'application/json',
data: JSON.stringify(formData),
success: () => {
alert('修改成功!');
goBack();
},
error: (xhr) => {
alert(`修改失败:${xhr.responseJSON?.message || '服务器错误'}`);
}
});
});
// 金额输入验证
$('#dueAmount, #actualAmount').on('input', function() {
let value = parseFloat($(this).val());
if (value < 0) $(this).val(0); //
if ($(this).val() > 1000000) $(this).val(1000000); // 限制最大值
});
</script>
</body>
</html>

@ -0,0 +1,259 @@
<!DOCTYPE html>
<html>
<head>
<title>统计分析</title>
<!-- 引入Bootstrap -->
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
<!-- 引入Chart.js -->
<script src="https://cdn.bootcdn.net/ajax/libs/Chart.js/3.7.0/chart.min.js"></script>
<!-- 引入jQuery -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
<style>
.card { margin-bottom: 20px; }
.stats-value { font-size: 1.2em; font-weight: bold; }
.chart-container { position: relative; height: 300px; }
</style>
</head>
<body>
<!-- 主容器 -->
<div class="container mt-4">
<!-- 时间范围选择 -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">时间范围选择</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<div class="mb-3">
<label>开始日期</label>
<input type="date" id="startDate" class="form-control">
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label>结束日期</label>
<input type="date" id="endDate" class="form-control">
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label>&nbsp;</label>
<button class="btn btn-primary w-100" onclick="calculateStats()">统计</button>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<!-- 基础统计卡片 -->
<div class="col-md-6">
<div class="card">
<div class="card-header">基础统计</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<p>总记录数:<span class="stats-value" id="totalCount">-</span></p>
<p>总金额:<span class="stats-value" id="totalAmount">-</span></p>
<p>平均金额:<span class="stats-value" id="avgAmount">-</span></p>
</div>
<div class="col-md-6">
<p>已交齐人数:<span class="stats-value" id="paidCount">-</span></p>
<p>未交齐人数:<span class="stats-value" id="unpaidCount">-</span></p>
<p>缴纳率:<span class="stats-value" id="paymentRate">-</span></p>
</div>
</div>
</div>
</div>
</div>
<!-- 未缴纳统计卡片 -->
<div class="col-md-6">
<div class="card">
<div class="card-header">未缴纳人员统计</div>
<div class="card-body">
<button class="btn btn-danger mb-3" onclick="getUnpaidList()">统计未缴纳人员</button>
<div id="unpaidList" class="mt-3"></div>
</div>
</div>
</div>
</div>
<div class="row">
<!-- 按单位统计图表 -->
<div class="col-md-6">
<div class="card">
<div class="card-header">按单位统计</div>
<div class="card-body">
<div class="chart-container">
<canvas id="unitChart"></canvas>
</div>
</div>
</div>
</div>
<!-- 按月统计图表 -->
<div class="col-md-6">
<div class="card">
<div class="card-header">按月统计</div>
<div class="card-body">
<div class="chart-container">
<canvas id="monthlyChart"></canvas>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// 图表实例
let unitChart = null;
let monthlyChart = null;
// 格式化金额
function formatAmount(amount) {
return '¥' + amount.toFixed(2);
}
// 时间范围统计函数
function calculateStats() {
const start = $('#startDate').val();
const end = $('#endDate').val();
if (!start || !end) {
alert('请选择开始和结束日期');
return;
}
// 请求统计接口
$.get(`/api/stats?start=${start}&end=${end}`)
.done(data => {
// 更新基础统计
const basic = data.basic;
$('#totalCount').text(basic.total_count);
$('#totalAmount').text(formatAmount(basic.total_amount));
$('#avgAmount').text(formatAmount(basic.avg_amount));
$('#paidCount').text(basic.paid_count);
$('#unpaidCount').text(basic.unpaid_count);
$('#paymentRate').text(((basic.paid_count / basic.total_count) * 100).toFixed(1) + '%');
// 更新单位统计图表
updateUnitChart(data.by_unit);
// 更新月度统计图表
updateMonthlyChart(data.by_month);
})
.fail(() => {
alert('获取统计数据失败');
});
}
// 更新单位统计图表
function updateUnitChart(unitData) {
const ctx = document.getElementById('unitChart').getContext('2d');
if (unitChart) {
unitChart.destroy();
}
unitChart = new Chart(ctx, {
type: 'bar',
data: {
labels: unitData.map(item => item.unit),
datasets: [{
label: '缴纳金额',
data: unitData.map(item => item.total_amount),
backgroundColor: 'rgba(54, 162, 235, 0.5)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function(value) {
return '¥' + value;
}
}
}
}
}
});
}
// 更新月度统计图表
function updateMonthlyChart(monthlyData) {
const ctx = document.getElementById('monthlyChart').getContext('2d');
if (monthlyChart) {
monthlyChart.destroy();
}
monthlyChart = new Chart(ctx, {
type: 'line',
data: {
labels: monthlyData.map(item => item.month),
datasets: [{
label: '缴纳金额',
data: monthlyData.map(item => item.total_amount),
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
tension: 0.1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function(value) {
return '¥' + value;
}
}
}
}
}
});
}
// 未缴纳人员统计
function getUnpaidList() {
$.get('/api/unpaid')
.done(data => {
let html = `<p>未缴纳人数: ${data.count}</p><ul class="list-group">`;
data.users.forEach(user => {
html += `<li class="list-group-item">
${user.name} (${user.id}) - ${user.unit}
</li>`;
});
html += '</ul>';
$('#unpaidList').html(html);
})
.fail(() => {
alert('获取未缴纳人员列表失败');
});
}
// 页面加载时设置默认日期范围(当前月份)
$(document).ready(function() {
const now = new Date();
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
$('#startDate').val(firstDay.toISOString().split('T')[0]);
$('#endDate').val(lastDay.toISOString().split('T')[0]);
// 自动加载统计数据
calculateStats();
});
</script>
</body>
</html>

@ -0,0 +1,152 @@
<!DOCTYPE html>
<html>
<head>
<title>用户详情</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入Bootstrap和jQuery -->
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>
<style>
.user-info-item {
padding: 10px 0;
border-bottom: 1px solid #eee;
}
.user-info-item:last-child {
border-bottom: none;
}
.loading-spinner {
display: none;
text-align: center;
padding: 20px;
}
.error-message {
display: none;
color: #dc3545;
padding: 20px;
text-align: center;
}
</style>
</head>
<body>
<!-- 内容容器 -->
<div class="container mt-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>用户详细信息</h2>
<a href="{{ url_for('admin_main') }}" class="btn btn-secondary">
<i class="bi bi-arrow-left"></i> 返回
</a>
</div>
<!-- 加载提示 -->
<div id="loadingSpinner" class="loading-spinner">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">加载中...</span>
</div>
<p class="mt-2">正在加载用户信息...</p>
</div>
<!-- 错误提示 -->
<div id="errorMessage" class="error-message">
<div class="alert alert-danger" role="alert">
加载用户信息失败,请稍后重试
</div>
</div>
<!-- 用户信息卡片 -->
<div id="userInfo" class="card shadow-sm">
<!-- 动态内容将在此加载 -->
</div>
</div>
<script>
// 用户数据加载函数
function loadUserData() {
const urlParams = new URLSearchParams(window.location.search);//解析和操作url参数、window.location.search返回URL 中问号(?)后的查询字符串,例如 ?id=123。
const userId = urlParams.get('id');//获取URL中的id参数值
if (!userId) {
showError('未找到用户ID');
return;
}
// 显示加载状态
$('#loadingSpinner').show();
$('#userInfo').hide();
$('#errorMessage').hide();
// 请求用户数据
$.ajax({
url: `/api/user?id=${userId}`,
method: 'GET',
success: function(data) {
// 构建详情卡片
$('#userInfo').html(`
<div class="card-body">
<div class="row">
<div class="col-md-3 text-center mb-3">
<div class="avatar-placeholder bg-primary text-white rounded-circle d-flex align-items-center justify-content-center mx-auto" style="width: 100px; height: 100px; font-size: 2.5rem;">
${data.name.charAt(0)}
</div>
</div>
<div class="col-md-9">
<h4 class="card-title mb-4">${data.name}</h4>
<div class="user-info-item">
<strong>用户ID</strong> ${data.id}
</div>
<div class="user-info-item">
<strong>性别:</strong> ${data.gender}
</div>
<div class="user-info-item">
<strong>所属单位:</strong> ${data.unit}
</div>
<div class="user-info-item">
<strong>联系方式:</strong> ${data.contact}
</div>
<div class="user-info-item">
<strong>最后登录时间:</strong> ${formatDate(data.last_login)}
</div>
</div>
</div>
</div>
`);
$('#userInfo').show();
},
error: function(xhr, status, error) {
showError('加载用户信息失败:' + error);
},
complete: function() {
$('#loadingSpinner').hide();
}
});
}
// 显示错误信息
function showError(message) {
$('#errorMessage').html(`
<div class="alert alert-danger" role="alert">
${message}
</div>
`).show();
}
// 格式化日期
function formatDate(dateString) {
if (!dateString) return '暂无记录';
const date = new Date(dateString);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
// 页面加载时执行
$(document).ready(loadUserData);
</script>
</body>
</html>

@ -0,0 +1,161 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<!-- 基础元信息 -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>党员缴费平台</title>
<!-- 引入Bootstrap样式 -->
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
<!-- 自定义样式 -->
<style>
.function-area {
margin-top: 2rem;
padding: 1.5rem;
border: 1px solid #dee2e6;
border-radius: 8px;
}
.record-table {
margin-top: 1.5rem;
}
.status-paid { color: #28a745; } /* 已缴费状态绿色 */
.status-unpaid { color: #dc3545; } /* 未缴费状态红色 */
</style>
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-dark bg-primary"> /* 统一导航栏颜色为Bootstrap主蓝色 */
<div class="container-fluid">
<span class="navbar-brand">党员缴费系统</span>
<div class="d-flex align-items-center">
<!-- 当前用户显示 -->
<span class="text-white me-3" id="currentUser"></span>
<!-- 退出登录按钮 -->
<button class="btn btn-outline-light" onclick="logout()">退出登录</button>
</div>
</div>
</nav>
<div class="container">
<!-- 功能操作区 -->
<div class="function-area">
<div class="d-grid gap-3">
<!-- 缴费按钮 -->
<button class="btn btn-lg btn-primary" style="box-shadow: 0 2px 4px rgba(0,0,0,0.1);" onclick="gotoPayment()"> /* 调整按钮颜色并添加阴影 */
<i class="fas fa-money-bill-wave me-2"></i>立即缴费
</button>
<!-- 个人信息管理按钮 -->
<button class="btn btn-lg btn-secondary" style="box-shadow: 0 2px 4px rgba(0,0,0.1);" onclick="gotoProfile()"> /* 调整按钮颜色为辅助色 */
<i class="fas fa-user-edit me-2"></i>修改个人信息
</button>
<!-- 缴费记录查询按钮 -->
<button class="btn btn-lg btn-info" style="box-shadow: 0 2px 4px rgba(0,0,0.1);" onclick="loadRecords()"> /* 保持信息色并添加阴影 */
<i class="fas fa-file-invoice me-2"></i>查看缴费记录
</button>
</div>
</div>
<!-- 缴费记录显示区 -->
<div class="record-table">
<table class="table table-hover">
<thead class="table-dark">
<tr>
<th>缴费日期</th>
<th>应缴金额</th>
<th>实缴金额</th>
<th>缴费类型</th>
<th>状态</th>
</tr>
</thead>
<tbody id="paymentRecords">
<!-- 动态加载数据 -->
</tbody>
</table>
</div>
</div>
<!-- 引入依赖库 -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
<script>
// 页面初始化
$(document).ready(function() {
// 从sessionStorage获取登录信息
const userData = JSON.parse(sessionStorage.getItem('user'));
// 验证登录状态
// if (!userData || !userData.id) {
// alert('请先登录');
// window.location.href = 'login.html';
// return;
// }
// 显示当前用户
$('#currentUser').text(`${userData.name}${userData.id}`);
});
// 跳转缴费页面
function gotoPayment() {
window.location.href = 'payment.html';
}
// 跳转个人信息修改
function gotoProfile() {
window.location.href = 'profile.html';
}
// 加载缴费记录
function loadRecords() {
const user = JSON.parse(sessionStorage.getItem('user'));
$.ajax({
url: `/api/payments?userId=${user.id}`,
method: 'GET',
success: function(data) {
renderRecords(data);
},
error: function() {
alert('获取记录失败');
}
});
}
// 渲染缴费记录
function renderRecords(data) {
const tbody = $('#paymentRecords').empty();
data.forEach(item => {
const status = item.actual >= item.due ?
'<span class="status-paid">已交齐</span>' :
'<span class="status-unpaid">未交齐</span>';
tbody.append(`
<tr>
<td>${new Date(item.date).toLocaleDateString()}</td>
<td>¥${item.due.toFixed(2)}</td>
<td>¥${item.actual.toFixed(2)}</td>
<td>${typeMapping[item.type]}</td>
<td>${status}</td>
</tr>
`);
});
}
// 缴费类型映射
const typeMapping = {
monthly: '月度缴费',
special: '特殊党费',
other: '其他'
};
// 退出登录
function logout() {
sessionStorage.removeItem('user');
window.location.href = 'login.html';
}
</script>
</body>
</html>

@ -0,0 +1,630 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>军事代码合规性检查系统 - FortifyCode</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<!-- 顶部导航栏 -->
<nav class="navbar">
<div class="navbar-content">
<div class="logo">
<i class="fas fa-shield-alt"></i>
FortifyCode
</div>
<ul class="nav-menu">
<li><a href="#dashboard" class="nav-link active">仪表板</a></li>
<li><a href="#projects" class="nav-link">项目管理</a></li>
<li><a href="#reports" class="nav-link">规则集管理</a></li>
<li><a href="#settings" class="nav-link">配置中心</a></li>
</ul>
<div class="user-info">
<div class="notification">
<i class="fas fa-bell"></i>
<span class="notification-badge">3</span>
</div>
<div class="user-avatar">
<i class="fas fa-user-circle" style="font-size: 24px;"></i>
</div>
</div>
</div>
</nav>
<!-- 主内容区域 -->
<div class="main-content">
<!-- 仪表板页面 -->
<div id="dashboard-page" class="page-content active">
<!-- 页面标题 -->
<div class="page-header fade-in">
<h1 class="page-title">军事代码合规性检查系统</h1>
<p class="page-subtitle">基于多 Agent 协同的智能代码安全检测平台</p>
</div>
<!-- 统计卡片 -->
<div class="stats-grid fade-in">
<div class="stat-card">
<div class="stat-icon primary">
<i class="fas fa-code"></i>
</div>
<div class="stat-content">
<h3 id="totalFiles">1,247</h3>
<p>已检查文件</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon success">
<i class="fas fa-check-circle"></i>
</div>
<div class="stat-content">
<h3 id="complianceRate">98.5%</h3>
<p>合规率</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon warning">
<i class="fas fa-exclamation-triangle"></i>
</div>
<div class="stat-content">
<h3 id="pendingIssues">23</h3>
<p>待修复问题</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon danger">
<i class="fas fa-bug"></i>
</div>
<div class="stat-content">
<h3 id="highRiskIssues">5</h3>
<p>高危漏洞</p>
</div>
</div>
</div>
<!-- 功能区域 -->
<div class="function-grid fade-in">
<!-- 代码上传和检查区域 -->
<div class="main-panel">
<div class="panel-header">
<h2 class="panel-title">快捷代码检查</h2>
<div class="filter-tabs">
<span class="filter-tab active">Python</span>
<span class="filter-tab">C++</span>
<span class="filter-tab">Java</span>
</div>
</div>
<div class="panel-content">
<div class="upload-area" id="uploadArea">
<div class="upload-icon">
<i class="fas fa-cloud-upload-alt"></i>
</div>
<div class="upload-text">拖拽文件到此处或点击上传</div>
<div class="upload-hint">支持 .py, .pyx, .pyi 文件,最大 100MB</div>
<input type="file" class="file-input" id="fileInput" multiple accept=".py,.pyx,.pyi">
</div>
<div class="progress-container" id="progressContainer" style="display: none;">
<div class="progress-bar">
<div class="progress-fill" id="progressFill" style="width: 0%;"></div>
</div>
<div class="progress-text" id="progressText">准备检查...</div>
</div>
<div style="margin-top: 20px; text-align: center;">
<button class="btn btn-primary" id="startCheckBtn" style="display: none;">
<i class="fas fa-play"></i> 开始检查
</button>
<button class="btn btn-secondary" id="stopCheckBtn" style="display: none;">
<i class="fas fa-stop"></i> 停止检查
</button>
</div>
</div>
</div>
<!-- 多Agent状态面板 -->
<div class="agent-status">
<div class="panel-header">
<h2 class="panel-title">Agent 状态</h2>
</div>
<div class="agent-list">
<div class="agent-item">
<div class="agent-avatar">A1</div>
<div class="agent-info">
<div class="agent-name">pylint Agent</div>
<span class="agent-status-badge status-idle">空闲</span>
</div>
</div>
<div class="agent-item">
<div class="agent-avatar">A2</div>
<div class="agent-info">
<div class="agent-name">flake8 Agent</div>
<span class="agent-status-badge status-idle">空闲</span>
</div>
</div>
<div class="agent-item">
<div class="agent-avatar">A3</div>
<div class="agent-info">
<div class="agent-name">bandit Agent</div>
<span class="agent-status-badge status-idle">空闲</span>
</div>
</div>
</div>
</div>
</div>
<!-- 检查结果区域 -->
<div class="results-section fade-in" id="resultsSection" style="display: none;">
<div class="results-header">
<h2 class="results-title">检查结果</h2>
<div class="filter-tabs">
<span class="filter-tab active" data-filter="all">全部</span>
<span class="filter-tab" data-filter="error">错误</span>
<span class="filter-tab" data-filter="warning">警告</span>
<span class="filter-tab" data-filter="info">信息</span>
</div>
</div>
<div class="results-content" id="resultsContent">
<!-- 结果项将通过 JavaScript 动态生成 -->
</div>
</div>
</div>
<!-- 项目管理页面 -->
<div id="projects-page" class="page-content">
<div class="page-header fade-in">
<h1 class="page-title">项目管理</h1>
<p class="page-subtitle">管理您的代码检查项目和任务</p>
</div>
<div class="projects-container">
<div class="projects-header">
<div class="projects-actions">
<button class="btn btn-primary" id="createProjectBtn">
<i class="fas fa-plus"></i> 新建项目
</button>
<button class="btn btn-secondary" id="importProjectBtn">
<i class="fas fa-upload"></i> 导入项目
</button>
</div>
<div class="projects-search">
<input type="text" placeholder="搜索项目..." class="search-input" id="projectSearch">
<i class="fas fa-search"></i>
</div>
</div>
<div class="projects-grid" id="projectsGrid">
<!-- 项目卡片将通过 JavaScript 动态生成 -->
</div>
</div>
</div>
<!-- 项目详情页面 -->
<div id="project-detail-page" class="page-content">
<div class="page-header fade-in">
<div style="display: flex; align-items: center; gap: 15px;">
<button class="btn btn-secondary" id="backToProjectsBtn">
<i class="fas fa-arrow-left"></i> 返回项目列表
</button>
<div>
<h1 class="page-title" id="projectDetailTitle">项目详情</h1>
<p class="page-subtitle" id="projectDetailSubtitle">查看项目信息和运行代码检查</p>
</div>
</div>
</div>
<div class="project-detail-container">
<div class="project-info-panel">
<div class="panel-header">
<h2 class="panel-title">项目信息</h2>
<div class="project-actions">
<button class="btn btn-primary" id="runCheckBtn">
<i class="fas fa-play"></i> 运行检查
</button>
<button class="btn btn-secondary" id="editProjectBtn">
<i class="fas fa-edit"></i> 编辑项目
</button>
</div>
</div>
<div class="panel-content" id="projectInfoContent">
<!-- 项目信息将通过 JavaScript 动态生成 -->
</div>
</div>
<div class="check-history-panel">
<div class="panel-header">
<h2 class="panel-title">检查历史</h2>
</div>
<div class="panel-content" id="checkHistoryContent">
<!-- 检查历史将通过 JavaScript 动态生成 -->
</div>
</div>
</div>
<!-- 检查结果详情 -->
<div class="check-results-panel" id="checkResultsPanel" style="display: none;">
<div class="panel-header">
<h2 class="panel-title">检查结果详情</h2>
<div class="filter-tabs">
<span class="filter-tab active" data-filter="all">全部</span>
<span class="filter-tab" data-filter="error">错误</span>
<span class="filter-tab" data-filter="warning">警告</span>
<span class="filter-tab" data-filter="info">信息</span>
</div>
</div>
<div class="panel-content" id="checkResultsContent">
<!-- 检查结果将通过 JavaScript 动态生成 -->
</div>
</div>
</div>
<!-- 检查报告页面 -->
<div id="reports-page" class="page-content">
<div class="page-header fade-in">
<h1 class="page-title">检查报告</h1>
<p class="page-subtitle">查看详细的代码检查报告和分析结果</p>
</div>
<div class="reports-container">
<div class="reports-filters">
<div class="filter-group">
<label>时间范围:</label>
<select class="filter-select" id="timeRange">
<option value="7">最近7天</option>
<option value="30">最近30天</option>
<option value="90">最近90天</option>
</select>
</div>
<div class="filter-group">
<label>项目:</label>
<select class="filter-select" id="projectFilter">
<option value="all">全部项目</option>
</select>
</div>
<div class="filter-group">
<label>严重程度:</label>
<select class="filter-select" id="severityFilter">
<option value="all">全部</option>
<option value="high">高危</option>
<option value="medium">中危</option>
<option value="low">低危</option>
</select>
</div>
</div>
<div class="reports-content">
<div class="reports-summary">
<div class="summary-card">
<h3>检查概览</h3>
<div class="summary-stats">
<div class="summary-item">
<span class="summary-label">总检查次数</span>
<span class="summary-value" id="totalChecks">156</span>
</div>
<div class="summary-item">
<span class="summary-label">发现问题</span>
<span class="summary-value" id="totalIssues">342</span>
</div>
<div class="summary-item">
<span class="summary-label">已修复</span>
<span class="summary-value" id="fixedIssues">298</span>
</div>
<div class="summary-item">
<span class="summary-label">修复率</span>
<span class="summary-value" id="fixRate">87.1%</span>
</div>
</div>
</div>
</div>
<div class="reports-list" id="reportsList">
<!-- 报告列表将通过 JavaScript 动态生成 -->
</div>
</div>
</div>
</div>
<!-- 系统设置页面 -->
<div id="settings-page" class="page-content">
<div class="page-header fade-in">
<h1 class="page-title">系统设置</h1>
<p class="page-subtitle">配置系统参数和检查规则</p>
</div>
<div class="settings-container">
<div class="settings-tabs">
<div class="settings-tab active" data-tab="general">常规设置</div>
<div class="settings-tab" data-tab="agents">Agent配置</div>
<div class="settings-tab" data-tab="rules">检查规则</div>
<div class="settings-tab" data-tab="security">安全设置</div>
</div>
<div class="settings-content">
<!-- 常规设置 -->
<div id="general-settings" class="settings-panel active">
<div class="settings-section">
<h3>基本配置</h3>
<div class="setting-item">
<label>系统名称</label>
<input type="text" value="军事代码合规性检查系统" class="setting-input">
</div>
<div class="setting-item">
<label>检查超时时间(秒)</label>
<input type="number" value="300" class="setting-input">
</div>
<div class="setting-item">
<label>最大文件大小MB</label>
<input type="number" value="100" class="setting-input">
</div>
</div>
<div class="settings-section">
<h3>通知设置</h3>
<div class="setting-item">
<label>
<input type="checkbox" checked> 检查完成时发送通知
</label>
</div>
<div class="setting-item">
<label>
<input type="checkbox" checked> 发现高危漏洞时发送通知
</label>
</div>
</div>
</div>
<!-- Agent配置 -->
<div id="agents-settings" class="settings-panel">
<div class="settings-section">
<h3>Agent配置</h3>
<div class="agent-config-list">
<div class="agent-config-item">
<div class="agent-config-header">
<h4>pylint Agent</h4>
<label class="switch">
<input type="checkbox" checked>
<span class="slider"></span>
</label>
</div>
<div class="agent-config-details">
<div class="setting-item">
<label>检查级别</label>
<select class="setting-select">
<option value="basic">基础</option>
<option value="standard" selected>标准</option>
<option value="strict">严格</option>
</select>
</div>
<div class="setting-item">
<label>超时时间(秒)</label>
<input type="number" value="60" class="setting-input">
</div>
</div>
</div>
<div class="agent-config-item">
<div class="agent-config-header">
<h4>flake8 Agent</h4>
<label class="switch">
<input type="checkbox" checked>
<span class="slider"></span>
</label>
</div>
<div class="agent-config-details">
<div class="setting-item">
<label>检查级别</label>
<select class="setting-select">
<option value="basic">基础</option>
<option value="standard" selected>标准</option>
<option value="strict">严格</option>
</select>
</div>
<div class="setting-item">
<label>超时时间(秒)</label>
<input type="number" value="45" class="setting-input">
</div>
</div>
</div>
<div class="agent-config-item">
<div class="agent-config-header">
<h4>bandit Agent</h4>
<label class="switch">
<input type="checkbox" checked>
<span class="slider"></span>
</label>
</div>
<div class="agent-config-details">
<div class="setting-item">
<label>检查级别</label>
<select class="setting-select">
<option value="basic">基础</option>
<option value="standard" selected>标准</option>
<option value="strict">严格</option>
</select>
</div>
<div class="setting-item">
<label>超时时间(秒)</label>
<input type="number" value="90" class="setting-input">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 检查规则 -->
<div id="rules-settings" class="settings-panel">
<div class="settings-section">
<h3>代码风格规则</h3>
<div class="rules-list">
<div class="rule-item">
<div class="rule-header">
<h4>行长度限制</h4>
<label class="switch">
<input type="checkbox" checked>
<span class="slider"></span>
</label>
</div>
<div class="rule-details">
<label>最大行长度:</label>
<input type="number" value="120" class="setting-input">
</div>
</div>
<div class="rule-item">
<div class="rule-header">
<h4>函数复杂度检查</h4>
<label class="switch">
<input type="checkbox" checked>
<span class="slider"></span>
</label>
</div>
<div class="rule-details">
<label>最大复杂度:</label>
<input type="number" value="10" class="setting-input">
</div>
</div>
<div class="rule-item">
<div class="rule-header">
<h4>命名规范检查</h4>
<label class="switch">
<input type="checkbox" checked>
<span class="slider"></span>
</label>
</div>
</div>
</div>
</div>
<div class="settings-section">
<h3>安全规则</h3>
<div class="rules-list">
<div class="rule-item">
<div class="rule-header">
<h4>SQL注入检查</h4>
<label class="switch">
<input type="checkbox" checked>
<span class="slider"></span>
</label>
</div>
</div>
<div class="rule-item">
<div class="rule-header">
<h4>硬编码密码检查</h4>
<label class="switch">
<input type="checkbox" checked>
<span class="slider"></span>
</label>
</div>
</div>
<div class="rule-item">
<div class="rule-header">
<h4>敏感信息泄露检查</h4>
<label class="switch">
<input type="checkbox" checked>
<span class="slider"></span>
</label>
</div>
</div>
</div>
</div>
</div>
<!-- 安全设置 -->
<div id="security-settings" class="settings-panel">
<div class="settings-section">
<h3>访问控制</h3>
<div class="setting-item">
<label>会话超时时间(分钟)</label>
<input type="number" value="30" class="setting-input">
</div>
<div class="setting-item">
<label>
<input type="checkbox" checked> 启用双因素认证
</label>
</div>
<div class="setting-item">
<label>
<input type="checkbox"> 记录所有操作日志
</label>
</div>
</div>
<div class="settings-section">
<h3>数据安全</h3>
<div class="setting-item">
<label>
<input type="checkbox" checked> 自动删除临时文件
</label>
</div>
<div class="setting-item">
<label>文件保留时间(天)</label>
<input type="number" value="30" class="setting-input">
</div>
</div>
</div>
</div>
<div class="settings-actions">
<button class="btn btn-primary">保存设置</button>
<button class="btn btn-secondary">重置默认</button>
</div>
</div>
</div>
</div>
<!-- 新建项目模态框 -->
<div id="createProjectModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>新建项目</h2>
<span class="close">&times;</span>
</div>
<div class="modal-body">
<form id="createProjectForm">
<div class="form-group">
<label for="projectName">项目名称 *</label>
<input type="text" id="projectName" name="name" required>
</div>
<div class="form-group">
<label for="projectDescription">项目描述</label>
<textarea id="projectDescription" name="description" rows="3"></textarea>
</div>
<div class="form-group">
<label>项目来源 *</label>
<div class="source-type-tabs">
<input type="radio" id="sourceGithub" name="source_type" value="github" checked>
<label for="sourceGithub">GitHub</label>
<input type="radio" id="sourceGitee" name="source_type" value="gitee">
<label for="sourceGitee">Gitee</label>
<input type="radio" id="sourceUpload" name="source_type" value="upload">
<label for="sourceUpload">文件上传</label>
</div>
</div>
<div class="form-group" id="gitUrlGroup">
<label for="gitUrl">Git URL *</label>
<input type="url" id="gitUrl" name="source_url" placeholder="https://github.com/username/repository.git">
</div>
<div class="form-group" id="fileUploadGroup" style="display: none;">
<label for="fileUpload">选择文件或文件夹</label>
<input type="file" id="fileUpload" name="files" multiple webkitdirectory>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" id="cancelCreateProject">取消</button>
<button type="button" class="btn btn-primary" id="confirmCreateProject">创建项目</button>
</div>
</div>
</div>
<script src="js/app.js"></script>
</body>
</html>

@ -0,0 +1,278 @@
{
"total_issues": 29,
"error_count": 0,
"warning_count": 29,
"info_count": 0,
"tools_status": {
"pylint": "completed",
"flake8": "completed",
"bandit": "completed"
},
"all_issues": [
{
"file": "C:\\Users\\51736\\AppData\\Local\\Temp\\tmpj1y1hh_8\\test_sample.py",
"line": 15,
"column": 0,
"type": "warning",
"message": "Line too long (153/100)",
"rule": "C0301",
"severity": "medium"
},
{
"file": "C:\\Users\\51736\\AppData\\Local\\Temp\\tmpj1y1hh_8\\test_sample.py",
"line": 48,
"column": 0,
"type": "warning",
"message": "Trailing whitespace",
"rule": "C0303",
"severity": "medium"
},
{
"file": "C:\\Users\\51736\\AppData\\Local\\Temp\\tmpj1y1hh_8\\test_sample.py",
"line": 51,
"column": 0,
"type": "warning",
"message": "Trailing whitespace",
"rule": "C0303",
"severity": "medium"
},
{
"file": "C:\\Users\\51736\\AppData\\Local\\Temp\\tmpj1y1hh_8\\test_sample.py",
"line": 15,
"column": 0,
"type": "warning",
"message": "Constant name \"very_long_variable_name_that_exceeds_the_recommended_line_length_limit\" doesn't conform to UPPER_CASE naming style",
"rule": "C0103",
"severity": "medium"
},
{
"file": "C:\\Users\\51736\\AppData\\Local\\Temp\\tmpj1y1hh_8\\test_sample.py",
"line": 38,
"column": 13,
"type": "warning",
"message": "Use of eval",
"rule": "W0123",
"severity": "medium"
},
{
"file": "C:\\Users\\51736\\AppData\\Local\\Temp\\tmpj1y1hh_8\\test_sample.py",
"line": 52,
"column": 4,
"type": "warning",
"message": "Missing function or method docstring",
"rule": "C0116",
"severity": "medium"
},
{
"file": "C:\\Users\\51736\\AppData\\Local\\Temp\\tmpj1y1hh_8\\test_sample.py",
"line": 46,
"column": 0,
"type": "warning",
"message": "Too few public methods (1/2)",
"rule": "R0903",
"severity": "medium"
},
{
"file": "C:\\Users\\51736\\AppData\\Local\\Temp\\tmpj1y1hh_8\\test_sample.py",
"line": 8,
"column": 0,
"type": "warning",
"message": "Unused import os",
"rule": "W0611",
"severity": "medium"
},
{
"file": "C:\\Users\\51736\\AppData\\Local\\Temp\\tmpj1y1hh_8\\test_sample.py",
"line": 9,
"column": 0,
"type": "warning",
"message": "Unused import sys",
"rule": "W0611",
"severity": "medium"
},
{
"file": "C:\\Users\\51736\\AppData\\Local\\Temp\\tmpj1y1hh_8\\test_sample.py",
"line": 11,
"column": 0,
"type": "warning",
"message": "Unused import subprocess",
"rule": "W0611",
"severity": "medium"
},
{
"file": "C:\\Users\\51736\\AppData\\Local\\Temp\\tmpj1y1hh_8\\test_sample.py",
"line": 12,
"column": 0,
"type": "warning",
"message": "Unused urlparse imported from urllib.parse",
"rule": "W0611",
"severity": "medium"
},
{
"file": "C",
"line": 0,
"column": 8,
"type": "warning",
"message": "1: F401 'os' imported but unused",
"rule": "1:",
"severity": "medium"
},
{
"file": "C",
"line": 0,
"column": 9,
"type": "warning",
"message": "1: F401 'sys' imported but unused",
"rule": "1:",
"severity": "medium"
},
{
"file": "C",
"line": 0,
"column": 11,
"type": "warning",
"message": "1: F401 'subprocess' imported but unused",
"rule": "1:",
"severity": "medium"
},
{
"file": "C",
"line": 0,
"column": 12,
"type": "warning",
"message": "1: F401 'urllib.parse.urlparse' imported but unused",
"rule": "1:",
"severity": "medium"
},
{
"file": "C",
"line": 0,
"column": 15,
"type": "warning",
"message": "80: E501 line too long (153 > 79 characters)",
"rule": "80:",
"severity": "medium"
},
{
"file": "C",
"line": 0,
"column": 17,
"type": "warning",
"message": "1: E302 expected 2 blank lines, found 1",
"rule": "1:",
"severity": "medium"
},
{
"file": "C",
"line": 0,
"column": 25,
"type": "warning",
"message": "1: E302 expected 2 blank lines, found 1",
"rule": "1:",
"severity": "medium"
},
{
"file": "C",
"line": 0,
"column": 31,
"type": "warning",
"message": "1: E302 expected 2 blank lines, found 1",
"rule": "1:",
"severity": "medium"
},
{
"file": "C",
"line": 0,
"column": 35,
"type": "warning",
"message": "1: E302 expected 2 blank lines, found 1",
"rule": "1:",
"severity": "medium"
},
{
"file": "C",
"line": 0,
"column": 41,
"type": "warning",
"message": "1: E302 expected 2 blank lines, found 1",
"rule": "1:",
"severity": "medium"
},
{
"file": "C",
"line": 0,
"column": 46,
"type": "warning",
"message": "1: E302 expected 2 blank lines, found 1",
"rule": "1:",
"severity": "medium"
},
{
"file": "C",
"line": 0,
"column": 48,
"type": "warning",
"message": "1: W293 blank line contains whitespace",
"rule": "1:",
"severity": "medium"
},
{
"file": "C",
"line": 0,
"column": 51,
"type": "warning",
"message": "1: W293 blank line contains whitespace",
"rule": "1:",
"severity": "medium"
},
{
"file": "C",
"line": 0,
"column": 56,
"type": "warning",
"message": "1: E305 expected 2 blank lines after class or function definition, found 1",
"rule": "1:",
"severity": "medium"
},
{
"file": "C:\\Users\\51736\\AppData\\Local\\Temp\\tmpj1y1hh_8\\test_sample.py",
"line": 11,
"column": 0,
"type": "warning",
"message": "Consider possible security implications associated with the subprocess module.",
"rule": "B404",
"severity": "low",
"confidence": "HIGH"
},
{
"file": "C:\\Users\\51736\\AppData\\Local\\Temp\\tmpj1y1hh_8\\test_sample.py",
"line": 21,
"column": 0,
"type": "warning",
"message": "Possible SQL injection vector through string-based query construction.",
"rule": "B608",
"severity": "medium",
"confidence": "LOW"
},
{
"file": "C:\\Users\\51736\\AppData\\Local\\Temp\\tmpj1y1hh_8\\test_sample.py",
"line": 38,
"column": 0,
"type": "warning",
"message": "Use of possibly insecure function - consider using safer ast.literal_eval.",
"rule": "B307",
"severity": "medium",
"confidence": "HIGH"
},
{
"file": "C:\\Users\\51736\\AppData\\Local\\Temp\\tmpj1y1hh_8\\test_sample.py",
"line": 43,
"column": 0,
"type": "warning",
"message": "Possible hardcoded password: 'admin123'",
"rule": "B105",
"severity": "low",
"confidence": "MEDIUM"
}
]
}

@ -0,0 +1,5 @@
Flask==2.3.3
Flask-CORS==4.0.0
PyMySQL==1.1.0
GitPython==3.1.40
Werkzeug==2.3.7

@ -398,12 +398,6 @@ body {
color: #999;
font-size: 12px;
margin-top: 5px;
transition: all 0.3s;
}
.result-location:hover {
transform: translateX(2px);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.result-actions {
@ -856,27 +850,6 @@ body {
background: white;
}
/* 模态框编辑器样式 */
#modalCodeEditor {
font-family: 'Courier New', 'Consolas', 'Monaco', monospace;
font-size: 14px;
line-height: 1.6;
tab-size: 4;
white-space: pre;
word-wrap: normal;
overflow-x: auto;
}
#modalCodeEditor:focus {
background: white;
box-shadow: inset 0 0 0 1px #1e3c72;
}
#modalCodeEditor::placeholder {
color: #999;
font-style: italic;
}
/* 成功按钮样式 */
.btn-success {
background: #28a745;
@ -1294,75 +1267,6 @@ input:checked + .slider:before {
padding: 20px;
}
/* 编辑器模态框特殊样式 */
#editorModal .modal-body {
padding: 0;
min-height: 0;
}
#editorModal .modal-content {
max-width: 1200px;
}
#editorModal .panel-header {
background: white;
border-bottom: 1px solid #e9ecef;
margin: 0;
}
#editorModal .file-info {
margin-top: 8px;
padding: 8px 12px;
background: #f8f9fa;
border-top: 1px solid #e9ecef;
}
#editorModal #modalSidebar {
background: white;
}
#editorModal .file-path {
background: #f8f9fa;
border-bottom: 1px solid #e9ecef;
font-family: 'Courier New', monospace;
font-size: 12px;
color: #666;
}
#editorModal #modalFileTree {
background: white;
padding: 8px;
}
#editorModal .file-item {
margin: 2px 0;
font-size: 13px;
}
#editorModal .file-item:hover {
background-color: #e3f2fd;
}
#editorModal .file-item.selected {
background-color: #1e3c72;
color: white;
}
#editorModal .file-item.selected .file-icon {
color: white;
}
/* 编辑模态框分栏与拖拽条 */
#modalResizer {
transition: background 0.1s;
}
#modalResizer:hover {
background: rgba(30,60,114,0.08);
}
.resizing #modalResizer {
background: rgba(30,60,114,0.15);
}
.modal-footer {
display: flex;
justify-content: flex-end;

@ -0,0 +1,361 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>基于多agent协同的军工Python代码合规性检查系统 - FortifyCode</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<!-- 顶部导航栏 -->
<nav class="navbar">
<div class="navbar-content">
<div class="logo">
<i class="fas fa-shield-alt"></i>
FortifyCode
</div>
<ul class="nav-menu">
<li><a href="#dashboard" class="nav-link active">仪表板</a></li>
<li><a href="#projects" class="nav-link">项目管理</a></li>
<li><a href="#rules" class="nav-link">规则集管理</a></li>
<li><a href="#settings" class="nav-link">配置中心</a></li>
</ul>
<div class="user-info">
<div class="notification">
<i class="fas fa-bell"></i>
<span class="notification-badge">3</span>
</div>
<div class="user-avatar">
<i class="fas fa-user-circle" style="font-size: 24px;"></i>
</div>
</div>
</div>
</nav>
<!-- 主内容区域 -->
<div class="main-content">
<!-- 仪表板页面 -->
<div id="dashboard-page" class="page-content active">
<!-- 页面标题 -->
<div class="page-header fade-in">
<h1 class="page-title">基于多agent协同的军工Python代码合规性检查系统</h1>
<p class="page-subtitle">基于多 Agent 协同的智能代码安全检测平台</p>
</div>
<!-- 统计卡片 -->
<div class="stats-grid fade-in">
<div class="stat-card">
<div class="stat-icon primary">
<i class="fas fa-code"></i>
</div>
<div class="stat-content">
<h3 id="totalFiles">1,247</h3>
<p>已检查文件</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon success">
<i class="fas fa-check-circle"></i>
</div>
<div class="stat-content">
<h3 id="complianceRate">98.5%</h3>
<p>合规率</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon warning">
<i class="fas fa-exclamation-triangle"></i>
</div>
<div class="stat-content">
<h3 id="pendingIssues">23</h3>
<p>待修复问题</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon danger">
<i class="fas fa-bug"></i>
</div>
<div class="stat-content">
<h3 id="highRiskIssues">5</h3>
<p>高危漏洞</p>
</div>
</div>
</div>
<!-- 功能区域 -->
<div class="function-grid fade-in">
<!-- 代码上传和检查区域 -->
<div class="main-panel">
<div class="panel-header">
<h2 class="panel-title">快捷代码检查</h2>
<div class="filter-tabs">
<span class="filter-tab active">Python</span>
</div>
</div>
<div class="panel-content">
<div class="upload-area" id="uploadArea">
<div class="upload-icon">
<i class="fas fa-cloud-upload-alt"></i>
</div>
<div class="upload-text">拖拽文件到此处或点击上传</div>
<div class="upload-hint">支持 .py, .pyx, .pyi 文件,最大 100MB</div>
<input type="file" class="file-input" id="fileInput" multiple accept=".py,.pyx,.pyi">
</div>
<div class="progress-container" id="progressContainer" style="display: none;">
<div class="progress-bar">
<div class="progress-fill" id="progressFill" style="width: 0%;"></div>
</div>
<div class="progress-text" id="progressText">准备检查...</div>
</div>
<div style="margin-top: 20px; text-align: center;">
<button class="btn btn-primary" id="startCheckBtn" style="display: none;">
<i class="fas fa-play"></i> 开始检查
</button>
<button class="btn btn-secondary" id="stopCheckBtn" style="display: none;">
<i class="fas fa-stop"></i> 停止检查
</button>
</div>
</div>
</div>
<!-- 多Agent状态面板 -->
<div class="agent-status">
<div class="panel-header">
<h2 class="panel-title">Agent 状态</h2>
</div>
<div class="agent-list">
<div class="agent-item">
<div class="agent-avatar">A1</div>
<div class="agent-info">
<div class="agent-name">pylint Agent</div>
<span class="agent-status-badge status-idle">空闲</span>
</div>
</div>
<div class="agent-item">
<div class="agent-avatar">A2</div>
<div class="agent-info">
<div class="agent-name">flake8 Agent</div>
<span class="agent-status-badge status-idle">空闲</span>
</div>
</div>
<div class="agent-item">
<div class="agent-avatar">A3</div>
<div class="agent-info">
<div class="agent-name">bandit Agent</div>
<span class="agent-status-badge status-idle">空闲</span>
</div>
</div>
</div>
</div>
</div>
<!-- 检查结果区域 -->
<div class="results-section fade-in" id="resultsSection" style="display: none;">
<div class="results-header">
<h2 class="results-title">检查结果</h2>
<div class="filter-tabs">
<span class="filter-tab active" data-filter="all">全部</span>
<span class="filter-tab" data-filter="error">错误</span>
<span class="filter-tab" data-filter="warning">警告</span>
<span class="filter-tab" data-filter="info">信息</span>
</div>
</div>
<div class="results-content" id="resultsContent">
<!-- 结果项将通过 JavaScript 动态生成 -->
</div>
</div>
</div>
<!-- 项目管理页面 -->
<div id="projects-page" class="page-content">
<div class="page-header fade-in">
<h1 class="page-title">项目管理</h1>
<p class="page-subtitle">管理您的代码检查项目和任务</p>
</div>
<div class="projects-container">
<div class="projects-header">
<div class="projects-actions">
<button class="btn btn-primary" id="createProjectBtn">
<i class="fas fa-plus"></i> 新建项目
</button>
</div>
<div class="projects-search">
<input type="text" placeholder="搜索项目..." class="search-input" id="projectSearch">
<i class="fas fa-search"></i>
</div>
</div>
<div class="projects-grid" id="projectsGrid">
<!-- 项目卡片将通过 JavaScript 动态生成 -->
</div>
</div>
</div>
<!-- 项目详情页面 -->
<div id="project-detail-page" class="page-content">
<div class="page-header fade-in">
<div style="display: flex; align-items: center; gap: 15px;">
<button class="btn btn-secondary" id="backToProjectsBtn">
<i class="fas fa-arrow-left"></i> 返回项目列表
</button>
<div>
<h1 class="page-title" id="projectDetailTitle">项目详情</h1>
<p class="page-subtitle" id="projectDetailSubtitle">查看项目信息和运行代码检查</p>
</div>
</div>
</div>
<div class="project-detail-container">
<div class="project-info-panel">
<div class="panel-header">
<h2 class="panel-title">项目信息</h2>
<div class="project-actions">
<button class="btn btn-primary" id="runCheckBtn">
<i class="fas fa-play"></i> 运行检查
</button>
<button class="btn btn-secondary" id="editProjectBtn">
<i class="fas fa-edit"></i> 编辑项目
</button>
<button class="btn btn-danger" id="deleteProjectBtn">
<i class="fas fa-trash"></i> 删除项目
</button>
</div>
</div>
<div class="panel-content" id="projectInfoContent">
<!-- 项目信息将通过 JavaScript 动态生成 -->
</div>
</div>
<div class="check-history-panel">
<div class="panel-header">
<h2 class="panel-title">检查历史</h2>
</div>
<div class="panel-content" id="checkHistoryContent">
<!-- 检查历史将通过 JavaScript 动态生成 -->
</div>
</div>
</div>
<!-- 文件浏览器面板 -->
<div class="file-browser-panel">
<div class="panel-header">
<div style="display: flex; align-items: center; gap: 15px;">
<h2 class="panel-title">文件浏览器</h2>
<div class="file-actions">
<button class="btn btn-primary" id="uploadFileBtn">
<i class="fas fa-upload"></i> 上传文件
</button>
<button class="btn btn-secondary" id="createFileBtn">
<i class="fas fa-plus"></i> 新建文件
</button>
<button class="btn btn-secondary" id="createFolderBtn">
<i class="fas fa-folder-plus"></i> 新建文件夹
</button>
</div>
</div>
<div class="file-path">
<span id="currentPath">/</span>
</div>
</div>
<div class="file-browser-content">
<div class="file-tree" id="fileTree">
<!-- 文件树将通过 JavaScript 动态生成 -->
</div>
</div>
</div>
<!-- 代码编辑器面板 -->
<div class="code-editor-panel" id="codeEditorPanel" style="display: none;">
<div class="panel-header">
<div style="display: flex; align-items: center; gap: 15px;">
<h2 class="panel-title" id="editorTitle">代码编辑器</h2>
<div class="editor-actions">
<button class="btn btn-success" id="saveFileBtn">
<i class="fas fa-save"></i> 保存
</button>
<button class="btn btn-secondary" id="closeEditorBtn">
<i class="fas fa-times"></i> 关闭
</button>
</div>
</div>
<div class="file-info">
<span id="currentFilePath">未选择文件</span>
</div>
</div>
<div class="editor-content">
<textarea id="codeEditor" placeholder="选择文件开始编辑..."></textarea>
</div>
</div>
<!-- 检查结果详情 -->
<div class="check-results-panel" id="checkResultsPanel" style="display: none;">
<div class="panel-header">
<h2 class="panel-title">检查结果详情</h2>
<div class="filter-tabs">
<span class="filter-tab active" data-filter="all">全部</span>
<span class="filter-tab" data-filter="error">错误</span>
<span class="filter-tab" data-filter="warning">警告</span>
<span class="filter-tab" data-filter="info">信息</span>
</div>
</div>
<div class="panel-content" id="checkResultsContent">
<!-- 检查结果将通过 JavaScript 动态生成 -->
</div>
</div>
</div>
</div>
<!-- 新建项目模态框 -->
<div id="createProjectModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>新建项目</h2>
<span class="close">&times;</span>
</div>
<div class="modal-body">
<form id="createProjectForm">
<div class="form-group">
<label for="projectName">项目名称 *</label>
<input type="text" id="projectName" name="name" required>
</div>
<div class="form-group">
<label for="projectDescription">项目描述</label>
<textarea id="projectDescription" name="description" rows="3"></textarea>
</div>
<div class="form-group">
<label>项目来源 *</label>
<div class="source-type-tabs">
<input type="radio" id="sourceGithub" name="source_type" value="github" checked>
<label for="sourceGithub">GitHub</label>
<input type="radio" id="sourceGitee" name="source_type" value="gitee">
<label for="sourceGitee">Gitee</label>
<input type="radio" id="sourceUpload" name="source_type" value="upload">
<label for="sourceUpload">文件上传</label>
</div>
</div>
<div class="form-group" id="gitUrlGroup">
<label for="gitUrl">Git URL *</label>
<input type="url" id="gitUrl" name="source_url" placeholder="https://github.com/username/repository.git">
</div>
<div class="form-group" id="fileUploadGroup" style="display: none;">
<label for="fileUpload">选择文件或文件夹</label>
<input type="file" id="fileUpload" name="files" multiple webkitdirectory>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" id="cancelCreateProject">取消</button>
<button type="button" class="btn btn-primary" id="confirmCreateProject">创建项目</button>
</div>
</div>
</div>
<script src="js/app.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

@ -1,320 +0,0 @@
# Ollama 本地AI模型部署指南
## 一、前提条件
1. ✅ 已安装 Ollama您已完成
2. ✅ 已下载并运行至少一个模型(如 llama2、qwen、mistral 等)
3. ✅ Ollama 服务正在运行(默认端口 11434
## 二、验证 Ollama 安装
### 1. 检查 Ollama 服务状态
打开命令行,运行:
```bash
# Windows PowerShell
curl http://localhost:11434/api/tags
# 或者使用浏览器访问
# http://localhost:11434/api/tags
```
如果返回模型列表,说明 Ollama 正常运行。
### 2. 测试模型是否可用
```bash
# 测试模型响应(使用 curl
curl http://localhost:11434/api/generate -d "{
\"model\": \"llama2\",
\"prompt\": \"Hello\",
\"stream\": false
}"
```
**注意**:将 `llama2` 替换为您实际下载的模型名称。
## 三、在系统中配置 Ollama
### 方法一通过Web界面配置推荐
1. **启动系统**
```bash
# 运行启动脚本
start_server.bat
```
2. **打开配置中心**
- 在浏览器中访问:`http://localhost:5000`
- 点击顶部导航栏的 **"配置中心"**
- 切换到 **"本地模型"** 标签页
3. **添加 Ollama 模型**
- 点击 **"上传模型"** 或 **"添加模型"** 按钮
- 填写以下信息:
- **模型名称**:输入您在 Ollama 中下载的模型名称(如 `llama2`、`qwen`、`mistral` 等)
- **模型描述**:可选,如 "Ollama本地模型 - Llama2"
- **API地址**`http://localhost:11434/v1`
- **重要**:必须是 `/v1` 结尾,这是 OpenAI 兼容格式的端点
- 点击 **"确认添加"**
4. **启用本地AI**
- 切换到 **"AI配置"** 标签页
- 勾选 **"使用本地AI模型"**
- 在下拉菜单中选择刚才添加的模型
- 点击 **"保存配置"**
5. **测试连接**
- 在本地模型列表中,找到您添加的模型
- 点击 **"测试连接"** 按钮
- 如果显示 "连接成功",说明配置正确
### 方法二:手动配置文件(高级)
如果Web界面无法使用可以手动编辑配置文件
1. **创建配置文件目录**(如果不存在)
```
config/
├── ai_config.json
└── local_models.json
```
2. **编辑 `config/local_models.json`**
```json
[
{
"id": "ollama-llama2",
"name": "llama2",
"description": "Ollama本地模型 - Llama2",
"apiUrl": "http://localhost:11434/v1",
"createdAt": "2025-01-XX"
}
]
```
3. **编辑 `config/ai_config.json`**
```json
{
"useLocalAi": true,
"selectedModelId": "ollama-llama2"
}
```
4. **重启服务器**
- 停止当前运行的服务器Ctrl+C
- 重新运行 `start_server.bat`
## 四、常见模型配置示例
### 1. Llama2
```json
{
"id": "ollama-llama2",
"name": "llama2",
"description": "Meta Llama2 模型",
"apiUrl": "http://localhost:11434/v1"
}
```
### 2. Qwen通义千问
```json
{
"id": "ollama-qwen",
"name": "qwen",
"description": "阿里通义千问模型",
"apiUrl": "http://localhost:11434/v1"
}
```
### 3. Mistral
```json
{
"id": "ollama-mistral",
"name": "mistral",
"description": "Mistral AI 模型",
"apiUrl": "http://localhost:11434/v1"
}
```
### 4. CodeLlama代码专用
```json
{
"id": "ollama-codellama",
"name": "codellama",
"description": "CodeLlama 代码生成模型",
"apiUrl": "http://localhost:11434/v1"
}
```
## 五、下载推荐的模型
对于代码质量分析,推荐使用以下模型:
### 1. CodeLlama推荐
```bash
ollama pull codellama
```
- **优点**:专门针对代码优化,理解代码结构更好
- **大小**:约 3.8GB7B参数版本
### 2. Qwen中文推荐
```bash
ollama pull qwen
```
- **优点**:中文理解能力强,适合中文项目
- **大小**:约 4.4GB7B参数版本
### 3. Llama2通用
```bash
ollama pull llama2
```
- **优点**:通用性强,性能稳定
- **大小**:约 3.8GB7B参数版本
### 4. Mistral高性能
```bash
ollama pull mistral
```
- **优点**:性能优秀,响应速度快
- **大小**:约 4.1GB7B参数版本
## 六、验证配置
### 1. 检查配置是否正确
启动服务器后,查看控制台输出,应该看到:
```
[AI分析] 使用提供商: 本地模型
[AI分析] 本地模型: llama2 (http://localhost:11434/v1)
```
### 2. 测试代码分析功能
1. 创建一个测试项目
2. 运行代码检查
3. 查看AI分析结果
如果AI分析正常工作说明配置成功。
## 七、常见问题排查
### 问题1连接失败
**症状**:测试连接时显示 "连接失败"
**解决方案**
1. 确认 Ollama 服务正在运行
```bash
# 检查进程Windows
tasklist | findstr ollama
```
2. 确认端口 11434 未被占用
```bash
# 检查端口Windows
netstat -ano | findstr 11434
```
3. 确认API地址正确`http://localhost:11434/v1`(注意 `/v1` 结尾)
4. 检查防火墙是否阻止了连接
### 问题2模型名称不匹配
**症状**:配置后无法使用,提示模型不存在
**解决方案**
1. 查看已下载的模型列表
```bash
ollama list
```
2. 确保配置中的模型名称与 Ollama 中的名称完全一致(区分大小写)
3. 如果模型名称不同,重新添加模型配置
### 问题3响应速度慢
**症状**AI分析需要很长时间
**解决方案**
1. 使用更小的模型(如 7B 参数版本)
2. 确保有足够的系统内存(建议至少 8GB
3. 如果使用 GPU确保 GPU 驱动正确安装
4. 考虑使用 CodeLlama 等针对代码优化的模型
### 问题4内存不足
**症状**Ollama 崩溃或响应超时
**解决方案**
1. 关闭其他占用内存的程序
2. 使用更小的模型3B 或 7B 参数)
3. 增加系统虚拟内存
4. 如果使用 GPU确保显存足够
## 八、性能优化建议
### 1. 选择合适的模型
- **代码分析**:推荐 CodeLlama 或 Qwen
- **中文项目**:推荐 Qwen
- **快速响应**:推荐 Mistral 或较小的模型
### 2. 系统要求
- **最低配置**8GB RAMCPU 模式
- **推荐配置**16GB RAMGPU 加速NVIDIA GPU
- **最佳配置**32GB+ RAM高端 GPU
### 3. GPU 加速(可选)
如果您的系统有 NVIDIA GPU可以启用 GPU 加速:
1. 安装 CUDA 和 cuDNN
2. Ollama 会自动检测并使用 GPU
3. 查看 GPU 使用情况:
```bash
# Windows
nvidia-smi
```
## 九、下一步
配置完成后,您可以:
1. ✅ 在代码检查中使用本地AI分析
2. ✅ 享受完全离线的代码质量分析
3. ✅ 保护代码隐私(数据不出本地)
4. ✅ 根据需要切换不同的模型
## 十、快速参考
### 常用命令
```bash
# 查看已安装的模型
ollama list
# 下载新模型
ollama pull <model-name>
# 运行模型测试
ollama run <model-name>
# 查看模型信息
ollama show <model-name>
```
### 配置文件位置
- AI配置`config/ai_config.json`
- 本地模型列表:`config/local_models.json`
### API端点
- Ollama API`http://localhost:11434/v1`
- OpenAI兼容端点`http://localhost:11434/v1/chat/completions`
---
**提示**:如果遇到问题,请检查服务器控制台的错误日志,通常会有详细的错误信息。

@ -1,267 +0,0 @@
# FortifyCode - 基于多Agent协同的军工Python代码合规性检查系统
## 项目简介
FortifyCode 是一个集成了多个代码质量检查工具Pylint、Flake8、Bandit的 Web 应用,专为军工 Python 代码合规性检查而设计。
## 功能特性
- ✅ **多工具协同检查**:整合 Pylint、Flake8、Bandit 三大代码检查工具
- ✅ **可视化界面**:直观的 Web 界面,实时显示检查进度和结果
- ✅ **项目管理**:支持创建、管理多个代码检查项目
- ✅ **文件浏览器**:在线浏览和编辑项目文件
- ✅ **智能分类**:自动分类错误、警告和信息级别的问题
- ✅ **多种来源**:支持 GitHub、Gitee 克隆或直接文件上传
## 技术栈
### 前端
- HTML5 + CSS3 + JavaScript (原生)
- Font Awesome 图标库
- 响应式设计
### 后端
- Node.js + Express
- Multer (文件上传)
- CORS (跨域支持)
### 代码检查工具
- **Pylint**: Python 代码质量和风格检查
- **Flake8**: PEP 8 编码规范检查
- **Bandit**: 安全漏洞扫描
## 系统要求
- Node.js >= 14.0
- Python >= 3.7
- npm 或 yarn 包管理器
## 安装步骤
### 1. 克隆项目
```bash
git clone <repository-url>
cd FortifyCode_guoning/src
```
### 2. 安装 Node.js 依赖
```bash
npm install
```
### 3. 安装 Python 检查工具
```bash
pip install pylint flake8 bandit
```
或者使用 requirements.txt如果提供
```bash
pip install -r requirements.txt
```
## 运行项目
### Windows 系统
双击运行 `start_server.bat` 或在命令行中执行:
```cmd
start_server.bat
```
### Linux/Mac 系统
```bash
chmod +x start_server.sh
./start_server.sh
```
### 手动启动
```bash
node backend.js
```
## 访问系统
启动成功后,在浏览器中访问:
- **前端界面**: http://localhost:5000
- **API 接口**: http://localhost:5000/api
## 使用说明
### 快捷代码检查
1. 点击上传区域或拖拽 Python 文件(.py, .pyx, .pyi
2. 点击"开始检查"按钮
3. 等待检查完成,查看结果
### 项目管理
1. 进入"项目管理"页面
2. 点击"新建项目"
3. 选择项目来源GitHub、Gitee 或文件上传)
4. 填写项目信息并创建
5. 在项目详情页面进行代码检查和文件管理
### 查看检查结果
检查完成后,结果会显示在页面下方,包括:
- 问题总数统计
- 错误、警告、信息分类
- 具体问题位置(文件、行号、列号)
- 问题描述和修复建议
## API 接口文档
### 文件上传
```
POST /api/upload
Content-Type: multipart/form-data
参数:
- files: 文件数组
响应:
{
"success": true,
"data": {
"temp_path": "临时目录路径",
"files": ["文件列表"]
}
}
```
### 代码检查
```
POST /api/check
Content-Type: application/json
参数:
{
"temp_path": "临时目录路径"
}
响应:
{
"success": true,
"data": {
"tools_status": {...},
"all_issues": [...],
"total_issues": 数量,
"error_count": 数量,
"warning_count": 数量,
"info_count": 数量
}
}
```
### 项目管理
```
GET /api/projects - 获取所有项目
POST /api/projects - 创建新项目
GET /api/projects/:id - 获取项目详情
DELETE /api/projects/:id - 删除项目
POST /api/projects/:id/check - 运行项目检查
```
更多 API 详情请查看 `view/API.md`
## 项目结构
```
src/
├── backend.js # 后端服务器主文件
├── frontend/ # 前端文件
│ ├── index.html # 主页面
│ ├── css/
│ │ └── style.css # 样式文件
│ └── js/
│ └── app.js # 前端逻辑
├── projects_data/ # 项目数据存储目录(自动创建)
├── out/ # 检查报告输出目录
├── start_server.bat # Windows 启动脚本
├── start_server.sh # Linux/Mac 启动脚本
├── package.json # Node.js 依赖配置
└── README.md # 本文档
```
## 常见问题
### Q: 提示找不到 Python 模块?
A: 确保已正确安装 pylint、flake8 和 bandit
```bash
pip install pylint flake8 bandit
```
### Q: 端口 5000 被占用?
A: 修改 `backend.js` 中的 PORT 变量,或设置环境变量:
```bash
PORT=8080 node backend.js
```
### Q: 文件上传失败?
A: 检查以下几点:
- 文件大小不超过 100MB
- 文件格式为 .py, .pyx, .pyi
- 临时目录有写入权限
### Q: 检查工具报错?
A: 确保 Python 和检查工具在系统 PATH 中:
```bash
python -m pylint --version
python -m flake8 --version
python -m bandit --version
```
## 开发说明
### 修改端口
编辑 `backend.js`
```javascript
const PORT = process.env.PORT || 5000; // 修改为其他端口
```
### 添加新的检查工具
`backend.js``TOOL_CONFIG` 中添加配置:
```javascript
newtool: {
command: '命令',
args: (filePath) => `参数 ${filePath}`,
parseResult: (stdout) => {
// 解析逻辑
return issues;
}
}
```
## 贡献指南
欢迎提交 Issue 和 Pull Request
## 许可证
本项目采用 MIT 许可证。
## 联系方式
如有问题或建议,请联系项目维护者。
---
**注意**: 本系统用于代码质量检查,不应替代人工代码审查。建议结合实际项目需求和编码规范使用。

File diff suppressed because it is too large Load Diff

@ -1,4 +0,0 @@
{
"useLocalAi": false,
"selectedModelId": null
}

@ -1,10 +0,0 @@
[
{
"id": "model_1763556444768_81a9s6dh0",
"name": "qwen:7b",
"description": "测试",
"apiUrl": "http://localhost:11434/v1",
"createdAt": "2025-11-19T12:47:24.768Z",
"filePath": null
}
]

@ -1,660 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>基于AI智能分析与多工具协同的Python代码质量检查系统 - FortifyCode</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<!-- 顶部导航栏 -->
<nav class="navbar">
<div class="navbar-content">
<div class="logo">
<i class="fas fa-shield-alt"></i>
FortifyCode
</div>
<ul class="nav-menu">
<li><a href="#dashboard" class="nav-link active">仪表板</a></li>
<li><a href="#projects" class="nav-link">项目管理</a></li>
<li><a href="#rules" class="nav-link">规则集管理</a></li>
<li><a href="#settings" class="nav-link">配置中心</a></li>
</ul>
<div class="user-info">
<div class="notification">
<i class="fas fa-bell"></i>
<span class="notification-badge">3</span>
</div>
<div class="user-avatar">
<i class="fas fa-user-circle" style="font-size: 24px;"></i>
</div>
</div>
</div>
</nav>
<!-- 主内容区域 -->
<div class="main-content">
<!-- 仪表板页面 -->
<div id="dashboard-page" class="page-content active">
<!-- 页面标题 -->
<div class="page-header fade-in">
<h1 class="page-title">基于AI智能分析与多工具协同的Python代码质量检查系统</h1>
<p class="page-subtitle">基于多 Agent 协同的智能代码安全检测平台</p>
</div>
<!-- 统计卡片 -->
<div class="stats-grid fade-in">
<div class="stat-card">
<div class="stat-icon primary">
<i class="fas fa-folder"></i>
</div>
<div class="stat-content">
<h3 id="totalProjects">0</h3>
<p>项目总数</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon success">
<i class="fas fa-check-circle"></i>
</div>
<div class="stat-content">
<h3 id="complianceRate">0%</h3>
<p>合规率</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon warning">
<i class="fas fa-exclamation-triangle"></i>
</div>
<div class="stat-content">
<h3 id="pendingIssues">0</h3>
<p>待修复问题</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon danger">
<i class="fas fa-bug"></i>
</div>
<div class="stat-content">
<h3 id="highRiskIssues">0</h3>
<p>高危漏洞</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon primary">
<i class="fas fa-file-code"></i>
</div>
<div class="stat-content">
<h3 id="totalFiles">0</h3>
<p>已检查文件</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon success">
<i class="fas fa-tasks"></i>
</div>
<div class="stat-content">
<h3 id="checkedProjects">0</h3>
<p>已检查项目</p>
</div>
</div>
</div>
<!-- 功能区域 -->
<div class="function-grid fade-in" style="grid-template-columns: 1fr 1fr;">
<!-- 快速开始指南 -->
<div class="main-panel">
<div class="panel-header">
<h2 class="panel-title"><i class="fas fa-rocket"></i> 快速开始</h2>
</div>
<div class="panel-content">
<div style="line-height: 2;">
<div style="margin-bottom: 15px; padding: 12px; background: #f8f9fa; border-radius: 6px; border-left: 4px solid #1e3c72;">
<strong><i class="fas fa-folder-open"></i> 1. 导入项目</strong>
<p style="margin: 8px 0 0 0; color: #666; font-size: 14px;">进入"项目管理"页面,点击"导入文件夹为项目"按钮选择您的Python项目文件夹。</p>
</div>
<div style="margin-bottom: 15px; padding: 12px; background: #f8f9fa; border-radius: 6px; border-left: 4px solid #28a745;">
<strong><i class="fas fa-play"></i> 2. 运行检查</strong>
<p style="margin: 8px 0 0 0; color: #666; font-size: 14px;">在项目详情页点击"运行检查"按钮,系统将使用 pylint、flake8、bandit 进行代码质量检查。</p>
</div>
<div style="margin-bottom: 15px; padding: 12px; background: #f8f9fa; border-radius: 6px; border-left: 4px solid #ffc107;">
<strong><i class="fas fa-search"></i> 3. 查看结果</strong>
<p style="margin: 8px 0 0 0; color: #666; font-size: 14px;">检查完成后,在"检查结果详情"面板查看问题列表,点击"定位"按钮可跳转到问题代码位置。</p>
</div>
<div style="padding: 12px; background: #f8f9fa; border-radius: 6px; border-left: 4px solid #17a2b8;">
<strong><i class="fas fa-edit"></i> 4. 编辑修复</strong>
<p style="margin: 8px 0 0 0; color: #666; font-size: 14px;">使用"文件编辑"功能在线修改代码,保存后重新运行检查验证修复效果。</p>
</div>
</div>
</div>
</div>
<!-- 系统功能说明 -->
<div class="main-panel">
<div class="panel-header">
<h2 class="panel-title"><i class="fas fa-info-circle"></i> 系统功能</h2>
</div>
<div class="panel-content">
<div style="line-height: 2;">
<div style="margin-bottom: 12px; display: flex; align-items: start; gap: 10px;">
<i class="fas fa-check" style="color: #28a745; margin-top: 4px;"></i>
<div>
<strong>多工具协同检查</strong>
<p style="margin: 4px 0 0 0; color: #666; font-size: 13px;">集成 pylint、flake8、bandit 三大工具,全面检测代码质量和安全问题</p>
</div>
</div>
<div style="margin-bottom: 12px; display: flex; align-items: start; gap: 10px;">
<i class="fas fa-check" style="color: #28a745; margin-top: 4px;"></i>
<div>
<strong>自定义规则集</strong>
<p style="margin: 4px 0 0 0; color: #666; font-size: 13px;">支持上传 setup.cfg、.pylintrc、.flake8 等配置文件,灵活定制检查规则</p>
</div>
</div>
<div style="margin-bottom: 12px; display: flex; align-items: start; gap: 10px;">
<i class="fas fa-check" style="color: #28a745; margin-top: 4px;"></i>
<div>
<strong>在线代码编辑</strong>
<p style="margin: 4px 0 0 0; color: #666; font-size: 13px;">内置代码编辑器,支持文件浏览、编辑、保存,快速修复问题</p>
</div>
</div>
<div style="margin-bottom: 12px; display: flex; align-items: start; gap: 10px;">
<i class="fas fa-check" style="color: #28a745; margin-top: 4px;"></i>
<div>
<strong>精确定位问题</strong>
<p style="margin: 4px 0 0 0; color: #666; font-size: 13px;">检查结果精确到文件、行、列,一键定位到问题代码位置</p>
</div>
</div>
<div style="display: flex; align-items: start; gap: 10px;">
<i class="fas fa-check" style="color: #28a745; margin-top: 4px;"></i>
<div>
<strong>项目历史管理</strong>
<p style="margin: 4px 0 0 0; color: #666; font-size: 13px;">记录每次检查结果,追踪代码质量变化趋势</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 最近项目列表 -->
<div class="main-panel fade-in" style="margin-top: 20px;">
<div class="panel-header">
<h2 class="panel-title"><i class="fas fa-history"></i> 最近项目</h2>
<a href="#projects" class="btn btn-secondary" style="text-decoration: none; padding: 6px 12px; font-size: 12px;">查看全部</a>
</div>
<div class="panel-content">
<div id="recentProjectsList" style="min-height: 100px;">
<div style="text-align: center; padding: 40px; color: #999;">
<i class="fas fa-folder-open" style="font-size: 48px; margin-bottom: 15px; opacity: 0.3;"></i>
<p>暂无项目,请先导入项目</p>
</div>
</div>
</div>
</div>
</div>
<!-- 规则集管理页面 -->
<div id="rules-page" class="page-content">
<div class="page-header fade-in">
<h1 class="page-title">规则集管理</h1>
<p class="page-subtitle">上传、查看、编辑与删除自定义规则配置pylint/flake8 等)</p>
</div>
<div class="projects-container">
<div class="projects-header">
<div class="projects-actions">
<button class="btn btn-primary" id="uploadRuleBtn">
<i class="fas fa-upload"></i> 上传规则文件
</button>
<button class="btn btn-secondary" id="refreshRuleBtn">
<i class="fas fa-rotate"></i> 刷新
</button>
</div>
</div>
<div class="projects-grid" id="rulesList">
<!-- 规则项通过 JavaScript 动态生成 -->
</div>
</div>
</div>
<!-- 编辑规则文件模态框 -->
<div id="editRuleModal" class="modal" style="display: none;">
<div class="modal-content" style="max-width: 800px; width: 90%;">
<div class="modal-header">
<h2 id="editRuleTitle">编辑规则</h2>
<span class="close" id="closeEditRule">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label>内容</label>
<textarea id="editRuleTextarea" rows="18" style="width: 100%;"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" id="cancelEditRule">取消</button>
<button type="button" class="btn btn-success" id="saveRuleBtn"><i class="fas fa-save"></i>
保存</button>
</div>
</div>
</div>
<!-- 配置中心页面 -->
<div id="settings-page" class="page-content">
<div class="page-header fade-in">
<h1 class="page-title">配置中心</h1>
<p class="page-subtitle">管理系统配置和AI模型设置</p>
</div>
<div class="settings-container">
<div class="settings-tabs">
<div class="settings-tab active" data-tab="ai-config">
<i class="fas fa-robot"></i> AI配置
</div>
<div class="settings-tab" data-tab="local-models">
<i class="fas fa-server"></i> 本地模型
</div>
</div>
<div class="settings-content">
<!-- AI配置面板 -->
<div class="settings-panel active" id="ai-config-panel">
<div class="settings-section">
<h3><i class="fas fa-cog"></i> AI分析配置</h3>
<div class="form-group">
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer;">
<input type="checkbox" id="useLocalAi" style="width: auto; margin: 0; cursor: pointer;">
<span style="font-weight: 500;">使用本地AI模型</span>
</label>
<p style="margin-top: 8px; font-size: 12px; color: #666; line-height: 1.5;">
启用后系统将使用本地部署的AI模型进行分析而不是云端API。需要先上传并配置本地模型。
</p>
</div>
<div id="localModelSelectGroup" style="display: none; margin-top: 20px;">
<div class="form-group">
<label for="localModelSelect" style="font-weight: 500; margin-bottom: 8px; display: block;">
<i class="fas fa-list"></i> 选择本地模型
</label>
<select id="localModelSelect" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; background: white; cursor: pointer;">
<option value="">请选择本地模型...</option>
</select>
<p style="margin-top: 8px; font-size: 12px; color: #666; line-height: 1.5;">
<i class="fas fa-info-circle" style="color: #1e3c72; margin-right: 4px;"></i>
选择已上传的本地AI模型。如果列表为空请先上传模型。
</p>
</div>
</div>
<div id="cloudAiConfigGroup" style="margin-top: 20px;">
<div class="form-group">
<label style="font-weight: 500; margin-bottom: 12px; display: block;">
<i class="fas fa-cloud"></i> 云端AI配置
</label>
<div style="padding: 12px; background: #f8f9fa; border-radius: 6px; border: 1px solid #e9ecef;">
<p style="margin: 0 0 10px 0; font-size: 13px; color: #666;">
当前使用的云端AI服务<strong id="currentCloudProvider">未配置</strong>
</p>
<p style="margin: 0; font-size: 12px; color: #999;">
云端AI配置通过环境变量设置请修改启动脚本中的环境变量。
</p>
</div>
</div>
</div>
<div class="settings-actions" style="margin-top: 30px;">
<button type="button" class="btn btn-primary" id="saveAiConfigBtn">
<i class="fas fa-save"></i> 保存配置
</button>
<button type="button" class="btn btn-secondary" id="testAiConfigBtn">
<i class="fas fa-vial"></i> 测试连接
</button>
</div>
</div>
</div>
<!-- 本地模型管理面板 -->
<div class="settings-panel" id="local-models-panel">
<div class="settings-section">
<h3><i class="fas fa-server"></i> 本地模型管理</h3>
<div style="margin-bottom: 20px;">
<button type="button" class="btn btn-primary" id="uploadModelBtn">
<i class="fas fa-upload"></i> 上传模型
</button>
<button type="button" class="btn btn-secondary" id="refreshModelsBtn">
<i class="fas fa-rotate"></i> 刷新列表
</button>
</div>
<div id="localModelsList" style="min-height: 200px;">
<div style="text-align: center; padding: 40px; color: #999;">
<i class="fas fa-server" style="font-size: 48px; margin-bottom: 15px; opacity: 0.3;"></i>
<p>暂无本地模型,请先上传模型</p>
</div>
</div>
<div style="margin-top: 20px; padding: 15px; background: #f0f7ff; border-radius: 6px; border: 1px solid #b3d9ff;">
<h4 style="margin: 0 0 10px 0; font-size: 14px; color: #1e3c72;">
<i class="fas fa-info-circle"></i> 使用说明
</h4>
<ul style="margin: 0; padding-left: 20px; font-size: 12px; color: #666; line-height: 1.8;">
<li>本地模型需要部署Ollama服务推荐或兼容OpenAI API的本地服务</li>
<li>上传的模型文件将存储在本地,确保有足够的磁盘空间</li>
<li>支持的模型格式Ollama模型、GGUF格式等</li>
<li>模型文件较大,上传可能需要较长时间</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 上传模型模态框 -->
<div id="uploadModelModal" class="modal" style="display: none;">
<div class="modal-content" style="max-width: 600px;">
<div class="modal-header">
<h2>上传本地AI模型</h2>
<span class="close" id="closeUploadModelModal">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label for="modelName">模型名称 *</label>
<input type="text" id="modelName" name="modelName" required placeholder="例如: llama2, qwen, mistral">
</div>
<div class="form-group">
<label for="modelDescription">模型描述</label>
<textarea id="modelDescription" name="modelDescription" rows="3" placeholder="描述模型的用途和特点"></textarea>
</div>
<div class="form-group">
<label for="modelApiUrl">API地址 *</label>
<input type="text" id="modelApiUrl" name="modelApiUrl" required placeholder="http://localhost:11434/v1" value="http://localhost:11434/v1">
<p style="margin-top: 8px; font-size: 12px; color: #666;">
本地AI服务的API地址。Ollama默认地址http://localhost:11434/v1<br>
<strong>注意</strong>:系统会自动添加 /chat/completions 端点,您只需填写基础地址即可。
</p>
</div>
<div class="form-group">
<label for="modelFile">模型文件(可选)</label>
<input type="file" id="modelFile" name="modelFile" accept=".gguf,.bin,.safetensors">
<p style="margin-top: 8px; font-size: 12px; color: #666;">
如果使用Ollama模型文件会自动从Ollama服务加载无需上传。如果使用其他服务可以上传模型文件。
</p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" id="cancelUploadModel">取消</button>
<button type="button" class="btn btn-primary" id="confirmUploadModel">
<i class="fas fa-upload"></i> 上传
</button>
</div>
</div>
</div>
<!-- 项目管理页面 -->
<div id="projects-page" class="page-content">
<div class="page-header fade-in">
<h1 class="page-title">项目管理</h1>
<p class="page-subtitle">管理您的代码检查项目和任务</p>
</div>
<div class="projects-container">
<div class="projects-header">
<div class="projects-actions">
<button class="btn btn-secondary" id="importFolderProjectBtn">
<i class="fas fa-folder-open"></i> 导入文件夹为项目
</button>
</div>
<div class="projects-search">
<input type="text" placeholder="搜索项目..." class="search-input" id="projectSearch">
<i class="fas fa-search"></i>
</div>
</div>
<div class="projects-grid" id="projectsGrid">
<!-- 项目卡片将通过 JavaScript 动态生成 -->
</div>
</div>
</div>
<!-- 项目详情页面 -->
<div id="project-detail-page" class="page-content">
<div class="page-header fade-in">
<div style="display: flex; align-items: center; gap: 15px;">
<button class="btn btn-secondary" id="backToProjectsBtn">
<i class="fas fa-arrow-left"></i> 返回项目列表
</button>
<div>
<h1 class="page-title" id="projectDetailTitle">项目详情</h1>
<p class="page-subtitle" id="projectDetailSubtitle">查看项目信息和运行代码检查</p>
</div>
</div>
</div>
<div class="project-detail-container">
<div class="project-info-panel">
<div class="panel-header">
<h2 class="panel-title">项目信息</h2>
<div class="project-actions">
<button class="btn btn-primary" id="runCheckBtn">
<i class="fas fa-play"></i> 运行检查
</button>
<button class="btn btn-secondary" id="openEditorModalBtn">
<i class="fas fa-pen"></i> 文件编辑
</button>
<button class="btn btn-danger" id="deleteProjectBtn">
<i class="fas fa-trash"></i> 删除项目
</button>
</div>
</div>
<div class="panel-content" id="projectInfoContent">
<!-- 项目信息将通过 JavaScript 动态生成 -->
</div>
</div>
</div>
<!-- 检查结果详情 -->
<div class="check-results-panel" id="checkResultsPanel" style="display: none;">
<div class="panel-header">
<h2 class="panel-title">检查结果详情</h2>
<div style="display: flex; align-items: center; gap: 10px;">
<div class="filter-tabs">
<span class="filter-tab active" data-filter="all">全部</span>
<span class="filter-tab" data-filter="error">错误</span>
<span class="filter-tab" data-filter="warning">警告</span>
<span class="filter-tab" data-filter="info">信息</span>
</div>
<button id="downloadReportBtn" class="btn btn-primary" style="margin-left: auto;">
<i class="fas fa-download"></i> 下载报告
</button>
</div>
</div>
<div class="panel-content" id="checkResultsContent">
<!-- 检查结果将通过 JavaScript 动态生成 -->
</div>
</div>
</div>
</div>
<!-- 新建项目模态框 -->
<div id="createProjectModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>新建项目</h2>
<span class="close">&times;</span>
</div>
<div class="modal-body">
<form id="createProjectForm">
<div class="form-group">
<label for="projectName">项目名称 *</label>
<input type="text" id="projectName" name="name" required>
</div>
<div class="form-group">
<label for="projectDescription">项目描述</label>
<textarea id="projectDescription" name="description" rows="3"></textarea>
</div>
<div class="form-group">
<label>项目来源 *</label>
<div class="source-type-tabs">
<input type="radio" id="sourceGithub" name="source_type" value="github" checked>
<label for="sourceGithub">GitHub</label>
<input type="radio" id="sourceGitee" name="source_type" value="gitee">
<label for="sourceGitee">Gitee</label>
<input type="radio" id="sourceUpload" name="source_type" value="upload">
<label for="sourceUpload">文件上传</label>
</div>
</div>
<div class="form-group" id="gitUrlGroup">
<label for="gitUrl">Git URL *</label>
<input type="url" id="gitUrl" name="source_url"
placeholder="https://github.com/username/repository.git">
</div>
<div class="form-group" id="fileUploadGroup" style="display: none;">
<label for="fileUpload">选择文件或文件夹</label>
<input type="file" id="fileUpload" name="files" multiple webkitdirectory>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" id="cancelCreateProject">取消</button>
<button type="button" class="btn btn-primary" id="confirmCreateProject">创建项目</button>
</div>
</div>
</div>
<!-- 文件编辑模态框 -->
<div id="editorModal" class="modal" style="display: none;">
<div class="modal-content"
style="max-width: 1000px; width: 95%; height: 80vh; display: flex; flex-direction: column;">
<div class="modal-header">
<h2>项目文件编辑</h2>
<span class="close" id="closeEditorModal">&times;</span>
</div>
<div class="modal-body" style="flex:1; display:flex; overflow:hidden;">
<div id="modalSidebar" style="width: 320px; min-width: 220px; max-width: 60%; border-right: 1px solid #eee; display:flex; flex-direction:column;">
<div class="file-path" style="padding:8px 12px;">
<span id="modalCurrentPath">/</span>
</div>
<div class="file-tree" id="modalFileTree" style="flex:1; overflow:auto;"></div>
</div>
<div id="modalResizer" style="width:6px; cursor: col-resize; background: transparent; position: relative;">
<div style="position:absolute; top:0; bottom:0; left:2px; width:2px; background:#e5e7eb;"></div>
</div>
<div style="flex:1; display:flex; flex-direction:column;">
<div class="panel-header" style="padding:8px 12px;">
<div style="display:flex; align-items:center; gap:10px;">
<h3 class="panel-title" id="modalEditorTitle" style="margin:0;">代码编辑器</h3>
<div class="editor-actions" style="margin-left:auto; display:flex; gap:8px;">
<button class="btn btn-success" id="modalSaveFileBtn"><i class="fas fa-save"></i>
保存</button>
<button class="btn" id="modalCloseBtn"><i class="fas fa-times"></i> 关闭</button>
</div>
</div>
<div class="file-info"><span id="modalCurrentFilePath">未选择文件</span></div>
</div>
<div class="editor-content" style="flex:1; padding:0; position:relative; overflow:hidden; display:flex; flex-direction:column;">
<div id="codeEditorWrapper" style="flex:1; display:flex; overflow:hidden; position:relative; height:100%; align-items:stretch;">
<div id="lineNumbers" style="background:#f5f5f5; color:#999; padding:15px 8px 15px 15px; font-family:'Courier New', monospace; font-size:14px; line-height:1.6; text-align:right; user-select:none; border-right:1px solid #e0e0e0; min-width:50px; width:50px; overflow-y:scroll; overflow-x:hidden; flex-shrink:0;"></div>
<textarea id="modalCodeEditor" placeholder="选择文件开始编辑..." style="flex:1; width:100%; border:none; padding:15px 15px 15px 8px; font-family:'Courier New', monospace; font-size:14px; line-height:1.6; resize:none; outline:none; background:#fafafa; color:#333; tab-size:4; overflow-y:scroll; overflow-x:auto;"></textarea>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 规则集选择对话框 -->
<div id="ruleSetSelectModal" class="modal" style="display: none;">
<div class="modal-content" style="max-width: 500px;">
<div class="modal-header">
<h2>检查配置</h2>
<span class="close" id="closeRuleSetModal">&times;</span>
</div>
<div class="modal-body">
<p style="margin-bottom: 20px; color: #666; font-size: 14px;">
<i class="fas fa-info-circle" style="color: #1e3c72; margin-right: 8px;"></i>
选择检查工具和规则集配置。至少需要选择一个检查工具。
</p>
<!-- 工具选择 -->
<div class="form-group">
<label style="font-weight: 500; margin-bottom: 12px; display: block;">
<i class="fas fa-tools" style="color: #1e3c72; margin-right: 8px;"></i>
选择检查工具
</label>
<div style="display: flex; flex-direction: column; gap: 10px; padding: 12px; background: #f8f9fa; border-radius: 6px; border: 1px solid #e9ecef;">
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer;">
<input type="checkbox" id="toolPylint" class="tool-checkbox" value="pylint" checked style="width: auto; margin: 0; cursor: pointer;">
<span style="flex: 1;">
<strong>Pylint</strong>
<span style="color: #666; font-size: 12px; margin-left: 8px;">代码质量检查</span>
</span>
</label>
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer;">
<input type="checkbox" id="toolFlake8" class="tool-checkbox" value="flake8" checked style="width: auto; margin: 0; cursor: pointer;">
<span style="flex: 1;">
<strong>Flake8</strong>
<span style="color: #666; font-size: 12px; margin-left: 8px;">代码风格检查</span>
</span>
</label>
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer;">
<input type="checkbox" id="toolBandit" class="tool-checkbox" value="bandit" checked style="width: auto; margin: 0; cursor: pointer;">
<span style="flex: 1;">
<strong>Bandit</strong>
<span style="color: #666; font-size: 12px; margin-left: 8px;">安全漏洞检查</span>
</span>
</label>
</div>
</div>
<!-- 规则集选择 -->
<div class="form-group" style="margin-top: 20px;">
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer; padding: 12px; background: #f8f9fa; border-radius: 6px; border: 1px solid #e9ecef;">
<input type="checkbox" id="useCustomRuleSet" style="width: auto; margin: 0; cursor: pointer;">
<span style="font-weight: 500;">使用自定义规则集</span>
</label>
</div>
<div class="form-group" id="ruleSetSelectGroup" style="display: none; margin-top: 15px;">
<label for="ruleSetSelect" style="font-weight: 500; margin-bottom: 8px; display: block;">选择规则集</label>
<select id="ruleSetSelect" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; background: white; cursor: pointer;">
<option value="">请选择规则集...</option>
</select>
<p style="margin-top: 8px; font-size: 12px; color: #666; line-height: 1.5;">
<i class="fas fa-check-circle" style="color: #28a745; margin-right: 4px;"></i>
选中的规则集文件将应用于已选择的检查工具
</p>
</div>
<!-- AI分析选项 -->
<div class="form-group" style="margin-top: 20px; padding: 12px; background: #f0f7ff; border-radius: 6px; border: 1px solid #b3d9ff;">
<label style="display: flex; align-items: start; gap: 10px; cursor: pointer;">
<input type="checkbox" id="useAiAnalysis" checked style="width: auto; margin: 0; cursor: pointer; margin-top: 2px;">
<div style="flex: 1;">
<span style="font-weight: 500; display: flex; align-items: center; gap: 6px;">
<i class="fas fa-robot" style="color: #1e3c72;"></i>
启用AI智能分析
</span>
<p style="margin: 6px 0 0 0; font-size: 12px; color: #666; line-height: 1.5;">
自动去重、风险评估和生成修改建议。需要配置OPENAI_API_KEY环境变量才能使用完整AI功能。
</p>
</div>
</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" id="cancelRuleSetBtn">取消</button>
<button type="button" class="btn btn-primary" id="confirmRuleSetBtn">开始检查</button>
</div>
</div>
</div>
<script src="js/app.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

@ -1,202 +0,0 @@
const API_BASE_URL = window.location.origin + '/api';
function formatFileSize(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}
function showSuccessMessage(message) {
const messageDiv = document.createElement('div');
messageDiv.style.cssText = `position: fixed; top: 20px; right: 20px; background: #d4edda; color: #155724; border: 1px solid #c3e6cb; border-radius: 6px; padding: 15px 20px; z-index: 3000; box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: flex; align-items: center; gap: 10px;`;
messageDiv.innerHTML = `<i class="fas fa-check-circle"></i><span>${message}</span>`;
document.body.appendChild(messageDiv);
setTimeout(() => messageDiv.remove(), 3000);
}
function showErrorMessage(message) {
const messageDiv = document.createElement('div');
messageDiv.style.cssText = `position: fixed; top: 20px; right: 20px; background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; border-radius: 6px; padding: 15px 20px; z-index: 3000; box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: flex; align-items: center; gap: 10px;`;
messageDiv.innerHTML = `<i class="fas fa-exclamation-circle"></i><span>${message}</span>`;
document.body.appendChild(messageDiv);
setTimeout(() => messageDiv.remove(), 5000);
}
async function loadRules() {
try {
const response = await fetch(`${API_BASE_URL}/rules`);
const data = await response.json();
if (!data.success) throw new Error(data.error || '加载失败');
renderRulesList(data.data || []);
} catch (e) {
console.error('加载规则失败:', e);
showErrorMessage('加载规则失败');
}
}
function renderRulesList(files) {
const container = document.getElementById('rulesList');
if (!container) return;
container.innerHTML = '';
if (!files.length) {
container.innerHTML = '<div style="text-align:center; padding: 20px; color:#666;">暂无规则文件</div>';
return;
}
files.forEach(f => {
const card = document.createElement('div');
card.className = 'project-card';
const size = formatFileSize(f.size || 0);
card.innerHTML = `
<div class="project-header">
<div>
<div class="project-title">${f.name}</div>
<div class="project-description">大小: ${size} | 更新: ${new Date(f.modified_at).toLocaleString()}</div>
</div>
</div>
<div class="project-actions">
<button class="btn btn-secondary">编辑</button>
<button class="btn btn-primary">下载</button>
<button class="btn btn-danger">删除</button>
</div>
`;
const [editBtn, downloadBtn, deleteBtn] = card.querySelectorAll('.project-actions .btn');
editBtn.addEventListener('click', (e) => { e.stopPropagation(); editRule(f.name); });
downloadBtn.addEventListener('click', (e) => { e.stopPropagation(); downloadRule(f.name); });
deleteBtn.addEventListener('click', (e) => { e.stopPropagation(); deleteRule(f.name); });
container.appendChild(card);
});
}
function showRuleUploadDialog() {
const input = document.createElement('input');
input.type = 'file';
input.multiple = true;
input.accept = '.cfg,.ini,.toml,.yaml,.yml,.json,.flake8,.pylintrc,setup.cfg,pyproject.toml,tox.ini';
input.onchange = async (e) => {
const files = Array.from(e.target.files || []);
if (!files.length) return;
const formData = new FormData();
files.forEach(f => formData.append('files', f));
try {
const resp = await fetch(`${API_BASE_URL}/rules/upload`, { method: 'POST', body: formData });
const data = await resp.json();
if (data.success) {
showSuccessMessage('规则上传成功');
loadRules();
} else {
showErrorMessage('规则上传失败:' + (data.error || ''));
}
} catch (e) {
console.error('上传规则失败:', e);
showErrorMessage('上传规则失败');
}
};
input.click();
}
let currentEditingRule = '';
async function editRule(name) {
try {
const resp = await fetch(`${API_BASE_URL}/rules/${encodeURIComponent(name)}`);
const data = await resp.json();
if (!data.success) throw new Error(data.error || '读取失败');
currentEditingRule = name;
document.getElementById('editRuleTitle').textContent = `编辑规则 - ${name}`;
document.getElementById('editRuleTextarea').value = data.data.content || '';
document.getElementById('editRuleModal').style.display = 'block';
} catch (e) {
console.error('读取规则失败:', e);
showErrorMessage('读取规则失败');
}
}
function hideEditRuleModal() {
const modal = document.getElementById('editRuleModal');
if (modal) modal.style.display = 'none';
currentEditingRule = '';
}
async function saveEditingRule() {
if (!currentEditingRule) return;
const content = document.getElementById('editRuleTextarea').value;
try {
const resp = await fetch(`${API_BASE_URL}/rules/${encodeURIComponent(currentEditingRule)}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content })
});
const data = await resp.json();
if (data.success) {
showSuccessMessage('保存成功');
hideEditRuleModal();
loadRules();
} else {
showErrorMessage('保存失败:' + (data.error || ''));
}
} catch (e) {
console.error('保存规则失败:', e);
showErrorMessage('保存失败');
}
}
async function deleteRule(name) {
if (!confirm(`确定删除规则文件 "${name}" 吗?`)) return;
try {
const resp = await fetch(`${API_BASE_URL}/rules/${encodeURIComponent(name)}`, { method: 'DELETE' });
const data = await resp.json();
if (data.success) {
showSuccessMessage('删除成功');
loadRules();
} else {
showErrorMessage('删除失败:' + (data.error || ''));
}
} catch (e) {
console.error('删除规则失败:', e);
showErrorMessage('删除失败');
}
}
async function downloadRule(name) {
try {
const resp = await fetch(`${API_BASE_URL}/rules/${encodeURIComponent(name)}`);
const data = await resp.json();
if (!data.success) throw new Error(data.error || '下载失败');
const blob = new Blob([data.data.content || ''], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = name;
document.body.appendChild(a);
a.click();
URL.revokeObjectURL(url);
a.remove();
} catch (e) {
console.error('下载规则失败:', e);
showErrorMessage('下载失败');
}
}
function bindRulesEvents() {
const uploadRuleBtn = document.getElementById('uploadRuleBtn');
const refreshRuleBtn = document.getElementById('refreshRuleBtn');
const closeEditRule = document.getElementById('closeEditRule');
const cancelEditRule = document.getElementById('cancelEditRule');
const saveRuleBtn = document.getElementById('saveRuleBtn');
if (uploadRuleBtn) uploadRuleBtn.addEventListener('click', showRuleUploadDialog);
if (refreshRuleBtn) refreshRuleBtn.addEventListener('click', loadRules);
if (closeEditRule) closeEditRule.addEventListener('click', hideEditRuleModal);
if (cancelEditRule) cancelEditRule.addEventListener('click', hideEditRuleModal);
if (saveRuleBtn) saveRuleBtn.addEventListener('click', saveEditingRule);
}
document.addEventListener('DOMContentLoaded', () => {
bindRulesEvents();
});
export { loadRules };

@ -1,5 +1,5 @@
{
"name": "fortifycode",
"name": "src",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
@ -931,26 +931,6 @@
"resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",

@ -0,0 +1,48 @@
# 代码质量检查报告
**文件:** test_sample.py
**检查时间:** 2025/9/18 14:28:54
## flake8 检查结果
### 输出:
```
D:\软件工程\代码质量检查\src\temp_check_1758176934520.py:3:1: E302 expected 2 blank lines, found 1
D:\软件工程\代码质量检查\src\temp_check_1758176934520.py:5:37: E261 at least two spaces before inline comment
D:\软件工程\代码质量检查\src\temp_check_1758176934520.py:7:1: E305 expected 2 blank lines after class or function definition, found 1
D:\软件工程\代码质量检查\src\temp_check_1758176934520.py:7:12: E225 missing whitespace around operator
```
### 解析结果:
```json
[
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758176934520.py",
"line": 3,
"column": 1,
"code": "E302",
"message": "expected 2 blank lines, found 1"
},
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758176934520.py",
"line": 5,
"column": 37,
"code": "E261",
"message": "at least two spaces before inline comment"
},
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758176934520.py",
"line": 7,
"column": 1,
"code": "E305",
"message": "expected 2 blank lines after class or function definition, found 1"
},
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758176934520.py",
"line": 7,
"column": 12,
"code": "E225",
"message": "missing whitespace around operator"
}
]
```

@ -0,0 +1,370 @@
# 代码质量检查报告
**文件:** test_sample.py
**检查时间:** 2025/9/18 14:33:21
## bandit 检查结果
### 输出:
```
{
"errors": [],
"generated_at": "2025-09-18T06:33:19Z",
"metrics": {
"C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177199344-test_sample.py": {
"CONFIDENCE.HIGH": 3,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 2,
"SEVERITY.MEDIUM": 1,
"SEVERITY.UNDEFINED": 0,
"loc": 6,
"nosec": 0,
"skipped_tests": 0
},
"_totals": {
"CONFIDENCE.HIGH": 3,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 2,
"SEVERITY.MEDIUM": 1,
"SEVERITY.UNDEFINED": 0,
"loc": 6,
"nosec": 0,
"skipped_tests": 0
}
},
"results": [
{
"code": "3 def bad_function():\n4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n",
"col_offset": 4,
"end_col_offset": 34,
"filename": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "MEDIUM",
"issue_text": "Use of exec detected.",
"line_number": 4,
"line_range": [
4
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b102_exec_used.html",
"test_id": "B102",
"test_name": "exec_used"
},
{
"code": "4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n6 \n",
"col_offset": 4,
"end_col_offset": 36,
"filename": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "LOW",
"issue_text": "Starting a process with a shell: Seems safe, but may be changed in the future, consider rewriting without shell",
"line_number": 5,
"line_range": [
5
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b605_start_process_with_a_shell.html",
"test_id": "B605",
"test_name": "start_process_with_a_shell"
},
{
"code": "4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n6 \n",
"col_offset": 4,
"end_col_offset": 36,
"filename": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "LOW",
"issue_text": "Starting a process with a partial executable path",
"line_number": 5,
"line_range": [
5
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b607_start_process_with_partial_path.html",
"test_id": "B607",
"test_name": "start_process_with_partial_path"
}
]
}
```
### 错误:
```
[main] INFO profile include tests: None
[main] INFO profile exclude tests: None
[main] INFO cli include tests: None
[main] INFO cli exclude tests: None
```
### 解析结果:
```json
{
"errors": [],
"generated_at": "2025-09-18T06:33:19Z",
"metrics": {
"C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177199344-test_sample.py": {
"CONFIDENCE.HIGH": 3,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 2,
"SEVERITY.MEDIUM": 1,
"SEVERITY.UNDEFINED": 0,
"loc": 6,
"nosec": 0,
"skipped_tests": 0
},
"_totals": {
"CONFIDENCE.HIGH": 3,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 2,
"SEVERITY.MEDIUM": 1,
"SEVERITY.UNDEFINED": 0,
"loc": 6,
"nosec": 0,
"skipped_tests": 0
}
},
"results": [
{
"code": "3 def bad_function():\n4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n",
"col_offset": 4,
"end_col_offset": 34,
"filename": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "MEDIUM",
"issue_text": "Use of exec detected.",
"line_number": 4,
"line_range": [
4
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b102_exec_used.html",
"test_id": "B102",
"test_name": "exec_used"
},
{
"code": "4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n6 \n",
"col_offset": 4,
"end_col_offset": 36,
"filename": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "LOW",
"issue_text": "Starting a process with a shell: Seems safe, but may be changed in the future, consider rewriting without shell",
"line_number": 5,
"line_range": [
5
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b605_start_process_with_a_shell.html",
"test_id": "B605",
"test_name": "start_process_with_a_shell"
},
{
"code": "4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n6 \n",
"col_offset": 4,
"end_col_offset": 36,
"filename": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "LOW",
"issue_text": "Starting a process with a partial executable path",
"line_number": 5,
"line_range": [
5
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b607_start_process_with_partial_path.html",
"test_id": "B607",
"test_name": "start_process_with_partial_path"
}
]
}
```
## flake8 检查结果
### 输出:
```
D:\软件工程\代码质量检查\src\temp_check_1758177199898.py:3:1: E302 expected 2 blank lines, found 1
D:\软件工程\代码质量检查\src\temp_check_1758177199898.py:5:37: E261 at least two spaces before inline comment
D:\软件工程\代码质量检查\src\temp_check_1758177199898.py:7:1: E305 expected 2 blank lines after class or function definition, found 1
D:\软件工程\代码质量检查\src\temp_check_1758177199898.py:7:12: E225 missing whitespace around operator
```
### 解析结果:
```json
[
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758177199898.py",
"line": 3,
"column": 1,
"code": "E302",
"message": "expected 2 blank lines, found 1"
},
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758177199898.py",
"line": 5,
"column": 37,
"code": "E261",
"message": "at least two spaces before inline comment"
},
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758177199898.py",
"line": 7,
"column": 1,
"code": "E305",
"message": "expected 2 blank lines after class or function definition, found 1"
},
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758177199898.py",
"line": 7,
"column": 12,
"code": "E225",
"message": "missing whitespace around operator"
}
]
```
## pylint 检查结果
### 输出:
```
[
{
"type": "convention",
"module": "1758177199344-test_sample",
"obj": "",
"line": 1,
"column": 0,
"endLine": null,
"endColumn": null,
"path": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"symbol": "missing-module-docstring",
"message": "Missing module docstring",
"message-id": "C0114"
},
{
"type": "convention",
"module": "1758177199344-test_sample",
"obj": "",
"line": 1,
"column": 0,
"endLine": null,
"endColumn": null,
"path": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"symbol": "invalid-name",
"message": "Module name \"1758177199344-test_sample\" doesn't conform to snake_case naming style",
"message-id": "C0103"
},
{
"type": "convention",
"module": "1758177199344-test_sample",
"obj": "bad_function",
"line": 3,
"column": 0,
"endLine": 3,
"endColumn": 16,
"path": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"symbol": "missing-function-docstring",
"message": "Missing function or method docstring",
"message-id": "C0116"
},
{
"type": "warning",
"module": "1758177199344-test_sample",
"obj": "bad_function",
"line": 4,
"column": 4,
"endLine": 4,
"endColumn": 34,
"path": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"symbol": "exec-used",
"message": "Use of exec",
"message-id": "W0122"
}
]
```
### 解析结果:
```json
[
{
"type": "convention",
"module": "1758177199344-test_sample",
"obj": "",
"line": 1,
"column": 0,
"endLine": null,
"endColumn": null,
"path": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"symbol": "missing-module-docstring",
"message": "Missing module docstring",
"message-id": "C0114"
},
{
"type": "convention",
"module": "1758177199344-test_sample",
"obj": "",
"line": 1,
"column": 0,
"endLine": null,
"endColumn": null,
"path": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"symbol": "invalid-name",
"message": "Module name \"1758177199344-test_sample\" doesn't conform to snake_case naming style",
"message-id": "C0103"
},
{
"type": "convention",
"module": "1758177199344-test_sample",
"obj": "bad_function",
"line": 3,
"column": 0,
"endLine": 3,
"endColumn": 16,
"path": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"symbol": "missing-function-docstring",
"message": "Missing function or method docstring",
"message-id": "C0116"
},
{
"type": "warning",
"module": "1758177199344-test_sample",
"obj": "bad_function",
"line": 4,
"column": 4,
"endLine": 4,
"endColumn": 34,
"path": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177199344-test_sample.py",
"symbol": "exec-used",
"message": "Use of exec",
"message-id": "W0122"
}
]
```

@ -0,0 +1,370 @@
# 代码质量检查报告
**文件:** test_sample.py
**检查时间:** 2025/9/18 14:35:53
## bandit 检查结果
### 输出:
```
{
"errors": [],
"generated_at": "2025-09-18T06:35:52Z",
"metrics": {
"C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177351895-test_sample.py": {
"CONFIDENCE.HIGH": 3,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 2,
"SEVERITY.MEDIUM": 1,
"SEVERITY.UNDEFINED": 0,
"loc": 6,
"nosec": 0,
"skipped_tests": 0
},
"_totals": {
"CONFIDENCE.HIGH": 3,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 2,
"SEVERITY.MEDIUM": 1,
"SEVERITY.UNDEFINED": 0,
"loc": 6,
"nosec": 0,
"skipped_tests": 0
}
},
"results": [
{
"code": "3 def bad_function():\n4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n",
"col_offset": 4,
"end_col_offset": 34,
"filename": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "MEDIUM",
"issue_text": "Use of exec detected.",
"line_number": 4,
"line_range": [
4
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b102_exec_used.html",
"test_id": "B102",
"test_name": "exec_used"
},
{
"code": "4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n6 \n",
"col_offset": 4,
"end_col_offset": 36,
"filename": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "LOW",
"issue_text": "Starting a process with a shell: Seems safe, but may be changed in the future, consider rewriting without shell",
"line_number": 5,
"line_range": [
5
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b605_start_process_with_a_shell.html",
"test_id": "B605",
"test_name": "start_process_with_a_shell"
},
{
"code": "4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n6 \n",
"col_offset": 4,
"end_col_offset": 36,
"filename": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "LOW",
"issue_text": "Starting a process with a partial executable path",
"line_number": 5,
"line_range": [
5
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b607_start_process_with_partial_path.html",
"test_id": "B607",
"test_name": "start_process_with_partial_path"
}
]
}
```
### 错误:
```
[main] INFO profile include tests: None
[main] INFO profile exclude tests: None
[main] INFO cli include tests: None
[main] INFO cli exclude tests: None
```
### 解析结果:
```json
{
"errors": [],
"generated_at": "2025-09-18T06:35:52Z",
"metrics": {
"C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177351895-test_sample.py": {
"CONFIDENCE.HIGH": 3,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 2,
"SEVERITY.MEDIUM": 1,
"SEVERITY.UNDEFINED": 0,
"loc": 6,
"nosec": 0,
"skipped_tests": 0
},
"_totals": {
"CONFIDENCE.HIGH": 3,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 2,
"SEVERITY.MEDIUM": 1,
"SEVERITY.UNDEFINED": 0,
"loc": 6,
"nosec": 0,
"skipped_tests": 0
}
},
"results": [
{
"code": "3 def bad_function():\n4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n",
"col_offset": 4,
"end_col_offset": 34,
"filename": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "MEDIUM",
"issue_text": "Use of exec detected.",
"line_number": 4,
"line_range": [
4
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b102_exec_used.html",
"test_id": "B102",
"test_name": "exec_used"
},
{
"code": "4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n6 \n",
"col_offset": 4,
"end_col_offset": 36,
"filename": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "LOW",
"issue_text": "Starting a process with a shell: Seems safe, but may be changed in the future, consider rewriting without shell",
"line_number": 5,
"line_range": [
5
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b605_start_process_with_a_shell.html",
"test_id": "B605",
"test_name": "start_process_with_a_shell"
},
{
"code": "4 exec(\"print('Hello, world!')\") # Potential security risk\n5 os.system(\"echo This is a test\")# Missing space after comment\n6 \n",
"col_offset": 4,
"end_col_offset": 36,
"filename": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "LOW",
"issue_text": "Starting a process with a partial executable path",
"line_number": 5,
"line_range": [
5
],
"more_info": "https://bandit.readthedocs.io/en/1.8.6/plugins/b607_start_process_with_partial_path.html",
"test_id": "B607",
"test_name": "start_process_with_partial_path"
}
]
}
```
## flake8 检查结果
### 输出:
```
D:\软件工程\代码质量检查\src\temp_check_1758177352260.py:3:1: E302 expected 2 blank lines, found 1
D:\软件工程\代码质量检查\src\temp_check_1758177352260.py:5:37: E261 at least two spaces before inline comment
D:\软件工程\代码质量检查\src\temp_check_1758177352260.py:7:1: E305 expected 2 blank lines after class or function definition, found 1
D:\软件工程\代码质量检查\src\temp_check_1758177352260.py:7:12: E225 missing whitespace around operator
```
### 解析结果:
```json
[
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758177352260.py",
"line": 3,
"column": 1,
"code": "E302",
"message": "expected 2 blank lines, found 1"
},
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758177352260.py",
"line": 5,
"column": 37,
"code": "E261",
"message": "at least two spaces before inline comment"
},
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758177352260.py",
"line": 7,
"column": 1,
"code": "E305",
"message": "expected 2 blank lines after class or function definition, found 1"
},
{
"file": "D:\\软件工程\\代码质量检查\\src\\temp_check_1758177352260.py",
"line": 7,
"column": 12,
"code": "E225",
"message": "missing whitespace around operator"
}
]
```
## pylint 检查结果
### 输出:
```
[
{
"type": "convention",
"module": "1758177351895-test_sample",
"obj": "",
"line": 1,
"column": 0,
"endLine": null,
"endColumn": null,
"path": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"symbol": "missing-module-docstring",
"message": "Missing module docstring",
"message-id": "C0114"
},
{
"type": "convention",
"module": "1758177351895-test_sample",
"obj": "",
"line": 1,
"column": 0,
"endLine": null,
"endColumn": null,
"path": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"symbol": "invalid-name",
"message": "Module name \"1758177351895-test_sample\" doesn't conform to snake_case naming style",
"message-id": "C0103"
},
{
"type": "convention",
"module": "1758177351895-test_sample",
"obj": "bad_function",
"line": 3,
"column": 0,
"endLine": 3,
"endColumn": 16,
"path": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"symbol": "missing-function-docstring",
"message": "Missing function or method docstring",
"message-id": "C0116"
},
{
"type": "warning",
"module": "1758177351895-test_sample",
"obj": "bad_function",
"line": 4,
"column": 4,
"endLine": 4,
"endColumn": 34,
"path": "C:\\Users\\\u5f20\u6d0b\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"symbol": "exec-used",
"message": "Use of exec",
"message-id": "W0122"
}
]
```
### 解析结果:
```json
[
{
"type": "convention",
"module": "1758177351895-test_sample",
"obj": "",
"line": 1,
"column": 0,
"endLine": null,
"endColumn": null,
"path": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"symbol": "missing-module-docstring",
"message": "Missing module docstring",
"message-id": "C0114"
},
{
"type": "convention",
"module": "1758177351895-test_sample",
"obj": "",
"line": 1,
"column": 0,
"endLine": null,
"endColumn": null,
"path": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"symbol": "invalid-name",
"message": "Module name \"1758177351895-test_sample\" doesn't conform to snake_case naming style",
"message-id": "C0103"
},
{
"type": "convention",
"module": "1758177351895-test_sample",
"obj": "bad_function",
"line": 3,
"column": 0,
"endLine": 3,
"endColumn": 16,
"path": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"symbol": "missing-function-docstring",
"message": "Missing function or method docstring",
"message-id": "C0116"
},
{
"type": "warning",
"module": "1758177351895-test_sample",
"obj": "bad_function",
"line": 4,
"column": 4,
"endLine": 4,
"endColumn": 34,
"path": "C:\\Users\\张洋\\AppData\\Local\\Temp\\1758177351895-test_sample.py",
"symbol": "exec-used",
"message": "Use of exec",
"message-id": "W0122"
}
]
```

@ -1,18 +1,17 @@
{
"name": "fortifycode",
"name": "src",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "fortifycode",
"name": "src",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"express": "^5.1.0",
"multer": "^2.0.2",
"ws": "^8.18.3"
"multer": "^2.0.2"
}
},
"node_modules/accepts": {
@ -942,26 +941,6 @@
"resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",
@ -1631,12 +1610,6 @@
"resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"ws": {
"version": "8.18.3",
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"requires": {}
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",

@ -1,22 +1,12 @@
{
"name": "fortifycode",
"name": "src",
"version": "1.0.0",
"description": "基于多Agent协同的军工Python代码合规性检查系统",
"main": "backend.js",
"description": "",
"main": "index.js",
"scripts": {
"start": "node backend.js",
"dev": "node backend.js",
"test": "echo \"Error: no test specified\" && exit 1",
"cleanup:dry": "node scripts/cleanup.js",
"cleanup": "node scripts/cleanup.js --execute"
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"code-quality",
"python",
"linter",
"security",
"compliance"
],
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {

@ -1,11 +0,0 @@
# FortifyCode Python 依赖
# 代码质量检查工具
pylint>=2.17.0
flake8>=6.0.0
bandit>=1.7.5
# 可选:增强功能
pycodestyle>=2.10.0
mccabe>=0.7.0

@ -1,62 +0,0 @@
[flake8]
# 基础规则配置
# 启用的检查规则集(默认包含 pycodestyle (E/W)、pyflakes (F),扩展支持 bugbear (B)、docstrings (D) 等)
select = E, W, F, B, D
# 忽略的特定错误/警告代码(根据团队习惯调整)
ignore =
# 行长度超过限制默认79下方已调整为120故忽略默认E501
E501,
# 与 black 格式化工具冲突的缩进检查black 推荐忽略)
E203,
# 多余的空白行警告(允许适当空行提高可读性)
W293,
# 变量未使用警告(临时变量或调试代码可能需要)
F841,
# bugbear 中过于严格的"表达式复杂度"警告(根据业务调整)
B019,
# 文档字符串相关:忽略模块级文档缺失(小型脚本可能不需要)
D100,
# 文档字符串相关:忽略私有函数文档缺失(内部函数可简化)
D105
# 最大行长度默认79调整为120更适合现代屏幕
max-line-length = 120
# 循环复杂度最大值(超过此值提示代码可能过于复杂,需拆分)
max-complexity = 12
# 排除不需要检查的文件/目录(如虚拟环境、自动生成的代码)
exclude =
.git,
__pycache__,
venv,
.venv,
migrations,
build,
dist,
*.egg-info
# 针对特定文件的单独忽略规则(例如测试文件放宽检查)
per-file-ignores =
# 测试文件忽略"函数参数未使用"(测试用例可能有占位参数)
tests/*.py: F841,
# 配置文件忽略"模块文档缺失"(配置文件通常无需文档)
config/*.py: D100
# 以下为插件扩展配置(需提前安装对应插件)
# 1. flake8-docstrings文档字符串检查需安装pip install flake8-docstrings
[flake8-docstrings]
# 文档字符串风格(支持 google/numpy/restructuredtext
docstring-style = google
# 允许无返回值函数省略 @return 说明
ignore-missing-docstrings = False # 全局不允许缺失文档字符串(除非在 ignore 中单独排除)
# 忽略"文档字符串末尾无空行"的警告(部分团队不强制)
ignore-decorators = abc.abstractmethod # 抽象方法无需重复文档
# 2. flake8-bugbear增强错误检查需安装pip install flake8-bugbear
[flake8-bugbear]
# 允许使用 assert 语句(默认 B011 警告 assert测试代码中常用故关闭
allow-asserts = True

@ -1,125 +0,0 @@
const express = require('express');
const fs = require('fs');
const path = require('path');
const multer = require('multer');
const router = express.Router();
const RULE_DIR = path.join(__dirname, '..', '..', 'rule');
if (!fs.existsSync(RULE_DIR)) {
fs.mkdirSync(RULE_DIR, { recursive: true });
}
const ruleStorage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, RULE_DIR);
},
filename: (req, file, cb) => {
cb(null, file.originalname);
}
});
const ruleUpload = multer({
storage: ruleStorage,
limits: { fileSize: 5 * 1024 * 1024 },
fileFilter: (req, file, cb) => {
const allowed = [
'setup.cfg', '.pylintrc', '.flake8', 'pyproject.toml',
'pylintrc', 'flake8.cfg', 'tox.ini'
];
const extAllowed = /\.(cfg|ini|toml|yaml|yml|json)$/i;
if (allowed.includes(file.originalname) || extAllowed.test(file.originalname)) {
return cb(null, true);
}
return cb(new Error('不支持的规则文件类型'));
}
});
router.post('/rules/upload', ruleUpload.array('files'), (req, res) => {
try {
if (!req.files || req.files.length === 0) {
return res.status(400).json({ success: false, error: '没有上传文件' });
}
const saved = req.files.map(f => f.originalname);
res.json({ success: true, data: { saved_files: saved, directory: RULE_DIR } });
} catch (error) {
console.error('规则文件上传失败:', error);
res.status(500).json({ success: false, error: error.message });
}
});
router.get('/rules', (req, res) => {
try {
if (!fs.existsSync(RULE_DIR)) {
return res.json({ success: true, data: [] });
}
const files = fs.readdirSync(RULE_DIR).map(name => {
const p = path.join(RULE_DIR, name);
const stat = fs.statSync(p);
return {
name,
size: stat.size,
modified_at: stat.mtime.toISOString(),
is_directory: stat.isDirectory()
};
}).filter(item => !item.is_directory);
res.json({ success: true, data: files });
} catch (error) {
console.error('读取规则列表失败:', error);
res.status(500).json({ success: false, error: error.message });
}
});
router.get('/rules/:name', (req, res) => {
try {
const name = req.params.name;
const target = path.join(RULE_DIR, name);
if (!fs.existsSync(target)) {
return res.status(404).json({ success: false, error: '规则文件不存在' });
}
const content = fs.readFileSync(target, 'utf8');
res.json({ success: true, data: { name, content } });
} catch (error) {
console.error('读取规则文件失败:', error);
res.status(500).json({ success: false, error: error.message });
}
});
router.put('/rules/:name', (req, res) => {
try {
const name = req.params.name;
const { content } = req.body || {};
if (!name) {
return res.status(400).json({ success: false, error: '规则文件名不能为空' });
}
const target = path.join(RULE_DIR, name);
const dir = path.dirname(target);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(target, content ?? '', 'utf8');
res.json({ success: true, message: '保存成功' });
} catch (error) {
console.error('保存规则文件失败:', error);
res.status(500).json({ success: false, error: error.message });
}
});
router.delete('/rules/:name', (req, res) => {
try {
const name = req.params.name;
const target = path.join(RULE_DIR, name);
if (!fs.existsSync(target)) {
return res.status(404).json({ success: false, error: '规则文件不存在' });
}
fs.unlinkSync(target);
res.json({ success: true, message: '已删除' });
} catch (error) {
console.error('删除规则文件失败:', error);
res.status(500).json({ success: false, error: error.message });
}
});
module.exports = router;

File diff suppressed because it is too large Load Diff

@ -1,100 +0,0 @@
@echo off
chcp 65001
echo ==========================================
echo FortifyCode 代码检查系统启动脚本
echo ==========================================
echo.
echo 正在检查 Node.js 环境...
where node >nul 2>nul
if %ERRORLEVEL% NEQ 0 (
echo [错误] 未找到 Node.js请先安装 Node.js
pause
exit /b 1
)
echo Node.js 版本:
node --version
echo.
echo 正在检查 Python 环境...
where python >nul 2>nul
if %ERRORLEVEL% NEQ 0 (
echo [错误] 未找到 Python请先安装 Python
pause
exit /b 1
)
echo Python 版本:
python --version
echo.
echo 正在检查依赖包...
if not exist "node_modules" (
echo [提示] 首次运行,正在安装 Node.js 依赖...
call npm install
if %ERRORLEVEL% NEQ 0 (
echo [错误] 依赖安装失败
pause
exit /b 1
)
)
echo.
echo 正在检查 Python 代码检查工具...
python -m pylint --version >nul 2>nul
if %ERRORLEVEL% NEQ 0 (
echo [警告] pylint 未安装,正在安装...
pip install pylint
)
python -m flake8 --version >nul 2>nul
if %ERRORLEVEL% NEQ 0 (
echo [警告] flake8 未安装,正在安装...
pip install flake8
)
python -m bandit --version >nul 2>nul
if %ERRORLEVEL% NEQ 0 (
echo [警告] bandit 未安装,正在安装...
pip install bandit
)
echo.
echo ==========================================
echo 配置AI分析服务...
echo ==========================================
echo.
echo 设置科大讯飞API配置...
set AI_PROVIDER=xf
REM HTTP API使用APIpasswordBearer Token在控制台获取https://console.xfyun.cn/services/bmx1
REM 如果使用WebSocket协议则需要XF_API_KEY和XF_API_SECRET
set XF_API_PASSWORD=mfxzmIRlAymtEmsgVojM:ZZIvBMLpPhAHSeATcoDY
REM 兼容旧配置如果XF_API_PASSWORD未设置会尝试使用XF_API_KEY
set XF_API_KEY=mfxzmIRlAymtEmsgVojM
set XF_API_SECRET=ZZIvBMLpPhAHSeATcoDY
set XF_MODEL=spark-x
set XF_API_URL=https://spark-api-open.xf-yun.com/v2/chat/completions
set AI_ANALYZER_ENABLED=true
echo [提示] AI分析服务已配置为使用科大讯飞Spark X1.5 HTTP API
echo [提示] 请确保XF_API_PASSWORD已正确配置HTTP协议的APIpassword
echo [提示] 获取地址https://console.xfyun.cn/services/bmx1
echo.
echo ==========================================
echo 启动服务器...
echo ==========================================
echo.
echo 服务器将在以下地址运行:
echo - 前端界面: http://localhost:5000
echo - API接口: http://localhost:5000/api
echo.
echo AI分析服务: 科大讯飞 Spark X1.5 API
echo.
echo 按 Ctrl+C 停止服务器
echo.
node backend.js
pause

@ -1,76 +0,0 @@
#!/bin/bash
echo "=========================================="
echo "FortifyCode 代码检查系统启动脚本"
echo "=========================================="
echo ""
echo "正在检查 Node.js 环境..."
if ! command -v node &> /dev/null; then
echo "[错误] 未找到 Node.js请先安装 Node.js"
exit 1
fi
echo "Node.js 版本:"
node --version
echo ""
echo "正在检查 Python 环境..."
if ! command -v python &> /dev/null && ! command -v python3 &> /dev/null; then
echo "[错误] 未找到 Python请先安装 Python"
exit 1
fi
PYTHON_CMD="python"
if command -v python3 &> /dev/null; then
PYTHON_CMD="python3"
fi
echo "Python 版本:"
$PYTHON_CMD --version
echo ""
echo "正在检查依赖包..."
if [ ! -d "node_modules" ]; then
echo "[提示] 首次运行,正在安装 Node.js 依赖..."
npm install
if [ $? -ne 0 ]; then
echo "[错误] 依赖安装失败"
exit 1
fi
fi
echo ""
echo "正在检查 Python 代码检查工具..."
$PYTHON_CMD -m pylint --version &> /dev/null
if [ $? -ne 0 ]; then
echo "[警告] pylint 未安装,正在安装..."
pip install pylint
fi
$PYTHON_CMD -m flake8 --version &> /dev/null
if [ $? -ne 0 ]; then
echo "[警告] flake8 未安装,正在安装..."
pip install flake8
fi
$PYTHON_CMD -m bandit --version &> /dev/null
if [ $? -ne 0 ]; then
echo "[警告] bandit 未安装,正在安装..."
pip install bandit
fi
echo ""
echo "=========================================="
echo "启动服务器..."
echo "=========================================="
echo ""
echo "服务器将在以下地址运行:"
echo " - 前端界面: http://localhost:5000"
echo " - API接口: http://localhost:5000/api"
echo ""
echo "按 Ctrl+C 停止服务器"
echo ""
node backend.js

@ -0,0 +1,80 @@
const { exec } = require('child_process');
const path = require('path');
// 测试Flake8的实际输出
function testFlake8() {
const testFile = path.join(__dirname, 'test_sample.py');
console.log('测试文件路径:', testFile);
// 测试1: 默认格式
console.log('\n=== 测试1: flake8 默认格式 ===');
exec(`flake8 "${testFile}"`, (error, stdout, stderr) => {
console.log('默认格式输出:');
console.log('stdout:', stdout);
console.log('stderr:', stderr);
});
// 测试2: JSON格式
console.log('\n=== 测试2: flake8 --format=json ===');
exec(`flake8 --format=json "${testFile}"`, (error, stdout, stderr) => {
console.log('JSON格式输出:');
console.log('stdout:', stdout);
console.log('stderr:', stderr);
// 尝试解析JSON
try {
if (stdout.trim()) {
const parsed = JSON.parse(stdout);
console.log('JSON解析成功:', parsed);
} else {
console.log('输出为空');
}
} catch (e) {
console.log('JSON解析失败:', e.message);
console.log('原始输出长度:', stdout.length);
console.log('输出前50个字符:', stdout.substring(0, 50));
}
});
// 测试3: quiet格式不使用JSON
console.log('\n=== 测试3: flake8 --quiet ===');
exec(`flake8 --quiet "${testFile}"`, (error, stdout, stderr) => {
console.log('quiet格式输出:');
console.log('stdout:', stdout);
console.log('stderr:', stderr);
});
// 测试4: 使用临时文件路径(模拟实际使用场景)
const tempFile = 'C:\\Users\\张洋\\AppData\\Local\\Temp\\test_temp_file.py';
console.log('\n=== 测试4: 使用临时文件路径 ===');
exec(`flake8 --quiet "${testFile}"`, (error, stdout, stderr) => {
console.log('临时文件格式输出:');
console.log('stdout:', stdout);
console.log('stderr:', stderr);
// 模拟我们的解析逻辑
if (stdout.trim()) {
const lines = stdout.trim().split('\n').filter(line => line.trim());
console.log('解析后的行数:', lines.length);
for (const line of lines) {
console.log('处理行:', line);
const match = line.match(/^(.+?):(\d+):(\d+):\s*([A-Z]\d+)\s*(.+)$/);
if (match) {
console.log('解析成功:', {
file: match[1],
line: parseInt(match[2]),
column: parseInt(match[3]),
code: match[4],
message: match[5]
});
} else {
console.log('解析失败:', line);
}
}
}
});
}
testFlake8();

@ -0,0 +1,31 @@
const path = require('path');
const fs = require('fs');
// 模拟backend.js中的路径计算
function testPathCalculation() {
// 模拟__dirname的值 (view目录)
const mockDirname = path.join(__dirname, 'view');
console.log('当前目录:', __dirname);
console.log('模拟的__dirname:', mockDirname);
// 计算out目录路径
const outDir = path.join(mockDirname, '..', 'out');
console.log('计算出的out目录路径:', outDir);
// 规范化路径
const normalizedOutDir = path.resolve(outDir);
console.log('规范化后的out目录路径:', normalizedOutDir);
// 检查目录是否存在
if (fs.existsSync(normalizedOutDir)) {
console.log('目录存在');
// 列出目录内容
const files = fs.readdirSync(normalizedOutDir);
console.log('目录内容:', files);
} else {
console.log('目录不存在');
}
}
testPathCalculation();

@ -0,0 +1,8 @@
import os
def bad_function():
exec("print('Hello, world!')") # Potential security risk
os.system("echo This is a test")# Missing space after comment
if __name__=="__main__": # Missing spaces around ==
bad_function()

@ -0,0 +1,42 @@
# 代码质量检查API文档
## API端点
- **URL**`/check`
- **方法**POST
- **请求格式**multipart/form-data
- **请求参数**
- `file` (文件)要检查的Python代码文件必需
- `tools` (字符串):逗号分隔的工具列表,例如 "bandit,flake8,pylint"(必需,至少一个)。
- **响应格式**JSON
- 成功200 OK
```
{
"results": [
{
"tool": "bandit",
"stdout": "...检查输出...",
"stderr": "...错误信息(如果有)..."
},
// 其他工具的结果
]
}
```
- 错误400 Bad Request
```
{
"error": "No file uploaded" // 或其他错误消息
}
```
- **示例请求** (使用curl)
```
curl -X POST http://localhost:3000/check \
-F "file=@D:\软件工程\代码质量检查\src\test_sample.py" \
-F "tools=bandit,flake8,pylint"
```
- **注意**
- 支持的工具bandit、flake8、pylint大小写不敏感
- 文件会临时保存并在检查后删除。
- 如果工具运行失败stderr会包含错误详情。

@ -0,0 +1,36 @@
# 代码质量检查后端启动手册
## 前提条件
- Node.js 已安装(版本 >= 14
- Python 3.9 已安装且bandit、flake8、pylint 通过pip安装。
- 依赖express、multer、cors运行`npm install express multer cors`安装)。
## 启动步骤
1. 导航到项目目录:
```
cd D:\软件工程\代码质量检查\src
```
2. 安装Node.js依赖如果尚未
```
npm install express multer cors
```
3. 启动后端服务器:
```
node view/backend.js
```
- 控制台会显示“Server running on port 3000”。
- 服务器监听http://localhost:3000。
4. 测试API
- 使用curl示例或前端页面如果有`view/frontend.html`,通过浏览器打开并提交)。
- 示例:上传`test_sample.py`选择所有工具检查响应JSON。
## 停止服务器
按Ctrl+C在终端中停止。
## 常见问题
- 如果端口3000被占用修改`app.listen(3000)`为其他端口如5000
- CORS错误确保cors已启用如果从本地文件测试前端使用Live Server扩展避免origin null。
- 工具失败检查Python PATH工具必须在系统PATH中可用

@ -0,0 +1,405 @@
const express = require('express');
const multer = require('multer');
const cors = require('cors');
const { exec } = require('child_process');
const fs = require('fs');
const path = require('path');
const os = require('os');
// 创建Express应用
const app = express();
const PORT = process.env.PORT || 3000;
// 配置CORS
app.use(cors({
origin: '*',
methods: ['GET', 'POST', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
preflightContinue: false,
optionsSuccessStatus: 204
}));
// 使用正则表达式处理所有OPTIONS请求
app.options(/.*/, (req, res) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.sendStatus(204);
});
// 解析JSON请求体
app.use(express.json());
// 请求日志中间件
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
next();
});
// 配置Multer文件上传
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, os.tmpdir());
},
filename: (req, file, cb) => {
cb(null, `${Date.now()}-${file.originalname}`);
}
});
const upload = multer({
storage: storage,
limits: { fileSize: 10 * 1024 * 1024 }
});
// 工具配置
const TOOL_CONFIG = {
bandit: {
command: 'bandit',
args: (filePath) => `-r -f json -o - ${filePath}`,
parser: (stdout) => JSON.parse(stdout)
},
flake8: {
command: 'flake8',
args: (filePath) => `${filePath}`,
parser: (stdout) => {
console.log('Flake8原始输出:', stdout);
console.log('Flake8输出长度:', stdout.length);
console.log('Flake8输出是否为空:', !stdout || stdout.trim() === '');
// 处理空输出情况
if (!stdout || stdout.trim() === '') {
console.log('Flake8输出为空返回空数组');
return []; // 返回空数组
}
// 检查是否已经是JSON格式
if (stdout.trim().startsWith('[') || stdout.trim().startsWith('{')) {
try {
return JSON.parse(stdout);
} catch (parseError) {
console.log('Flake8 JSON解析失败:', parseError.message);
}
}
// 如果不是JSON格式尝试解析为文本格式
console.log('Flake8输出不是JSON格式使用文本解析');
const lines = stdout.trim().split('\n').filter(line => line.trim());
const issues = [];
for (const line of lines) {
// 跳过注释行和空行
if (line.startsWith('#') || !line.trim()) {
continue;
}
// 尝试解析flake8的标准输出格式
// 支持绝对路径和相对路径,以及不同的分隔符
const match = line.match(/^(.+?):(\d+):(\d+):\s*([A-Z]\d+)\s*(.+)$/);
if (match) {
issues.push({
file: match[1],
line: parseInt(match[2]),
column: parseInt(match[3]),
code: match[4],
message: match[5]
});
} else if (line.includes(':')) {
// 如果不匹配标准格式,但包含冒号,尝试更宽松的解析
const parts = line.split(':');
if (parts.length >= 4) {
const file = parts[0];
const lineNum = parseInt(parts[1]);
const colNum = parseInt(parts[2]);
const codeAndMessage = parts.slice(3).join(':').trim();
// 尝试提取错误代码
const codeMatch = codeAndMessage.match(/^([A-Z]\d+)\s*(.+)$/);
if (codeMatch) {
issues.push({
file: file,
line: lineNum,
column: colNum,
code: codeMatch[1],
message: codeMatch[2]
});
} else {
issues.push({
file: file,
line: lineNum,
column: colNum,
code: 'UNKNOWN',
message: codeAndMessage
});
}
} else {
console.log('无法解析的Flake8输出行:', line);
}
} else {
// 如果行不包含任何错误信息,可能是其他类型的输出
console.log('跳过Flake8输出行:', line);
}
}
console.log('Flake8文本解析结果:', issues);
return issues;
}
},
pylint: {
command: 'pylint',
args: (filePath) => `--output-format=json ${filePath}`,
parser: (stdout) => {
// 处理空输出情况
if (!stdout || stdout.trim() === '') {
return []; // 返回空数组
}
return JSON.parse(stdout);
}
}
};
// 确保out目录存在放在项目根目录避免触发开发服务器重载
function ensureOutDir() {
// 将out目录放在项目根目录而不是view目录内
const outDir = path.join(__dirname, '..', 'out');
console.log(`检查输出目录: ${outDir}`);
if (!fs.existsSync(outDir)) {
console.log(`创建输出目录: ${outDir}`);
fs.mkdirSync(outDir, { recursive: true });
console.log(`输出目录创建成功`);
} else {
console.log(`输出目录已存在: ${outDir}`);
}
return outDir;
}
// 运行单个工具
function runTool(tool, filePath) {
return new Promise((resolve, reject) => {
const config = TOOL_CONFIG[tool];
if (!config) {
return reject(new Error(`不支持的工具: ${tool}`));
}
let actualFilePath = filePath;
let actualCommand = `${config.command} ${config.args(filePath)}`;
// 对于Flake8添加额外的调试和临时文件处理
if (tool === 'flake8') {
console.log(`Flake8原始命令: ${actualCommand}`);
console.log(`文件路径存在性: ${fs.existsSync(filePath)}`);
// 先检查文件内容
try {
const content = fs.readFileSync(filePath, 'utf8');
console.log(`Flake8检查的文件内容:\n${content}`);
// 创建本地副本进行检查
const localFileName = `temp_check_${Date.now()}.py`;
const localFilePath = path.join(process.cwd(), localFileName);
fs.writeFileSync(localFilePath, content);
console.log(`创建本地文件副本: ${localFilePath}`);
// 修改命令使用本地文件
actualFilePath = localFilePath;
actualCommand = `${config.command} ${config.args(localFilePath)}`;
console.log(`修改后的Flake8命令: ${actualCommand}`);
} catch (e) {
console.error('Flake8文件处理失败:', e);
}
}
console.log(`执行命令: ${actualCommand}`);
exec(actualCommand, { shell: true }, (error, stdout, stderr) => {
console.log(`${tool}执行结果 - 退出码: ${error ? error.code : 0}`);
let result = {
stdout: stdout ? stdout.trim() : '',
stderr: stderr ? stderr.trim() : ''
};
console.log(`${tool} stdout长度: ${result.stdout.length}`);
console.log(`${tool} stderr长度: ${result.stderr.length}`);
if (tool === 'flake8') {
console.log(`Flake8 stdout内容: "${result.stdout}"`);
console.log(`Flake8 stderr内容: "${result.stderr}"`);
// 清理本地临时文件
if (actualFilePath !== filePath) {
setTimeout(() => {
try {
if (fs.existsSync(actualFilePath)) {
fs.unlinkSync(actualFilePath);
console.log(`清理Flake8本地临时文件: ${actualFilePath}`);
}
} catch (e) {
console.error('清理Flake8本地临时文件失败:', e);
}
}, 1000);
}
}
try {
if (result.stdout) {
result.parsed = config.parser(result.stdout);
}
} catch (parseError) {
console.warn(`解析${tool}输出失败:`, parseError);
// 解析失败时返回原始输出
result.parsed = {
error: `解析失败: ${parseError.message}`,
rawOutput: result.stdout
};
}
resolve(result);
});
});
}
// 生成报告
function generateReport(results, filename) {
let report = `# 代码质量检查报告\n\n`;
report += `**文件:** ${filename}\n`;
report += `**检查时间:** ${new Date().toLocaleString()}\n\n`;
results.forEach(toolResult => {
report += `## ${toolResult.tool} 检查结果\n`;
if (toolResult.stdout) {
report += '### 输出:\n```\n';
report += toolResult.stdout;
report += '\n```\n\n';
}
if (toolResult.stderr) {
report += '### 错误:\n```\n';
report += toolResult.stderr;
report += '\n```\n\n';
}
if (toolResult.parsed) {
report += '### 解析结果:\n```json\n';
report += JSON.stringify(toolResult.parsed, null, 2);
report += '\n```\n\n';
}
});
return report;
}
// 健康检查端点
app.get('/health', (req, res) => {
res.status(200).json({
status: 'ok',
timestamp: new Date().toISOString()
});
});
// 检查端点
app.post('/check', upload.single('file'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: '未上传文件' });
}
const tools = req.body.tools ? req.body.tools.split(',') : ['bandit', 'flake8', 'pylint'];
const filePath = req.file.path;
const results = [];
// 调试:检查临时文件内容
console.log(`临时文件路径: ${filePath}`);
try {
const fileContent = fs.readFileSync(filePath, 'utf8');
console.log(`临时文件内容 (${fileContent.length} 字符):\n${fileContent}`);
} catch (error) {
console.error('读取临时文件失败:', error);
}
for (const tool of tools) {
console.log(`开始执行工具: ${tool}`);
const result = await runTool(tool, filePath);
console.log(`${tool} 执行结果 - stdout长度: ${result.stdout.length}, stderr长度: ${result.stderr.length}`);
results.push({
tool: tool,
stdout: result.stdout,
stderr: result.stderr,
parsed: result.parsed
});
}
const outDir = ensureOutDir();
const reportContent = generateReport(results, req.file.originalname);
const reportFilename = `report_${Date.now()}.md`;
const reportPath = path.join(outDir, reportFilename);
console.log(`正在生成报告文件: ${reportPath}`);
console.log(`报告内容长度: ${reportContent.length} 字符`);
fs.writeFileSync(reportPath, reportContent);
console.log(`报告文件生成成功: ${reportPath}`);
res.json({
success: true,
results: results,
reportUrl: `/reports/${reportFilename}`
});
} catch (error) {
console.error('检查错误:', error);
res.status(500).json({
success: false,
error: error.message
});
} finally {
if (req.file && fs.existsSync(req.file.path)) {
fs.unlink(req.file.path, (err) => {
if (err) console.error('删除临时文件失败:', err);
});
}
}
});
// 报告下载端点
app.get('/reports/:filename', (req, res) => {
const filename = req.params.filename;
// 从项目根目录的out文件夹读取文件
const filePath = path.join(__dirname, '..', 'out', filename);
if (fs.existsSync(filePath)) {
res.setHeader('Content-Type', 'text/markdown');
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
const fileStream = fs.createReadStream(filePath);
fileStream.pipe(res);
setTimeout(() => {
try {
fs.unlinkSync(filePath);
console.log(`已删除报告文件: ${filename}`);
} catch (err) {
console.error(`删除报告文件失败: ${err.message}`);
}
}, 5000);
} else {
res.status(404).json({
error: '报告未找到',
filename: filename
});
}
});
// 启动服务器
app.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}`);
console.log(`上传文件临时目录: ${os.tmpdir()}`);
console.log(`报告输出目录: ${path.join(__dirname, '..', 'out')}`);
});
module.exports = app;

@ -0,0 +1,575 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>代码质量检查工具</title>
<!-- 添加元标签防止缓存和刷新 -->
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<!-- 防止自动刷新 -->
<meta http-equiv="refresh" content="999999">
<style>
/* 保持之前的样式不变 */
body {
font-family: 'Microsoft YaHei', sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f8f9fa;
}
.container {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
margin-bottom: 20px;
}
h1 {
color: #2c3e50;
text-align: center;
margin-bottom: 30px;
}
.tool-checkbox {
margin: 15px 0;
padding: 10px;
background-color: #f1f8ff;
border-radius: 4px;
}
.file-upload {
margin: 20px 0;
}
.download-btn {
display: inline-block;
padding: 10px 20px;
background-color: #4CAF50;
color: white;
text-decoration: none;
border-radius: 4px;
font-weight: bold;
transition: background-color 0.3s;
margin-top: 15px;
}
.download-btn:hover {
background-color: #45a049;
}
#report-area {
margin-top: 20px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
display: none;
background-color: #e8f5e9;
}
#result {
background-color: #f5f5f5;
padding: 15px;
border-radius: 5px;
max-height: 400px;
overflow: auto;
white-space: pre-wrap;
font-family: monospace;
margin-top: 20px;
}
.loader {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin: 20px auto;
display: none;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.btn {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
.btn:hover {
background-color: #2980b9;
}
.btn:disabled {
background-color: #95a5a6;
cursor: not-allowed;
}
</style>
</head>
<body>
<div class="container">
<h1>代码质量检查工具</h1>
<div id="toolContainer">
<fieldset>
<legend>选择工具:</legend>
<div class="tool-checkbox">
<input type="checkbox" id="bandit" name="tools" value="bandit" checked>
<label for="bandit">Bandit</label>
</div>
<div class="tool-checkbox">
<input type="checkbox" id="flake8" name="tools" value="flake8" checked>
<label for="flake8">Flake8</label>
</div>
<div class="tool-checkbox">
<input type="checkbox" id="pylint" name="tools" value="pylint" checked>
<label for="pylint">Pylint</label>
</div>
</fieldset>
<div class="file-upload">
<label for="file">上传代码文件:</label>
<input type="file" id="file" name="file" accept=".py" aria-label="选择Python文件">
</div>
<button type="button" id="submitBtn" class="btn">提交检查</button>
<!-- 调试按钮 -->
<div style="margin-top: 20px; padding: 10px; background-color: #f0f0f0; border-radius: 4px;">
<button type="button" id="toggleRefreshBtn" class="btn"
style="background-color: #e74c3c;">启用防刷新</button>
<span id="refreshStatus" style="margin-left: 10px;">防刷新: 禁用</span>
</div>
</div>
<div class="loader" id="loader"></div>
<div id="report-area" aria-live="polite">
<h2>检查报告</h2>
<p>报告已生成,点击下载:</p>
<a id="report-download" href="#" class="download-btn">下载报告</a>
<button id="close-report" class="btn" style="margin-left: 10px;">关闭</button>
</div>
<h2>结果:</h2>
<pre id="result" role="log" aria-live="polite"></pre>
</div>
<script>
// 创建独立的事件处理模块
const App = (() => {
// DOM元素引用
const elements = {
submitBtn: document.getElementById('submitBtn'),
resultPre: document.getElementById('result'),
reportArea: document.getElementById('report-area'),
reportLink: document.getElementById('report-download'),
closeReportBtn: document.getElementById('close-report'),
loader: document.getElementById('loader'),
fileInput: document.getElementById('file'),
banditCheckbox: document.getElementById('bandit'),
flake8Checkbox: document.getElementById('flake8'),
pylintCheckbox: document.getElementById('pylint'),
toggleRefreshBtn: document.getElementById('toggleRefreshBtn'),
refreshStatus: document.getElementById('refreshStatus')
};
// 状态管理
let isProcessing = false;
let preventRefresh = false; // 防刷新状态
// 设置防刷新拦截器
const setupRefreshPrevention = () => {
// 拦截所有点击事件
document.addEventListener('click', function (event) {
if (preventRefresh) {
console.log('拦截到点击事件:', event.target.tagName, event.target.id || event.target.textContent);
event.preventDefault();
event.stopPropagation();
return false;
}
}, true);
// 拦截所有表单提交
document.addEventListener('submit', function (event) {
console.log('拦截到表单提交事件');
event.preventDefault();
event.stopPropagation();
return false;
}, true);
// 拦截键盘事件F5等
document.addEventListener('keydown', function (event) {
if (preventRefresh && (event.key === 'F5' || (event.ctrlKey && event.key === 'r') || (event.ctrlKey && event.key === 'R'))) {
console.log('拦截到刷新快捷键:', event.key);
event.preventDefault();
event.stopPropagation();
return false;
}
}, true);
// 拦截所有链接点击
document.addEventListener('click', function (event) {
if (preventRefresh && event.target.tagName === 'A') {
console.log('拦截到链接点击:', event.target.href);
// 只有非下载链接才阻止
if (!event.target.href.includes('reports/')) {
event.preventDefault();
event.stopPropagation();
return false;
}
}
}, true);
// 添加页面刷新监听器
window.addEventListener('beforeunload', function (event) {
console.log('页面即将被刷新或关闭');
if (preventRefresh) {
event.preventDefault();
event.returnValue = '';
return '';
}
});
};
// 初始化应用
const init = () => {
// 初始隐藏报告区域
elements.reportArea.style.display = 'none';
// 设置防刷新拦截器
setupRefreshPrevention();
// 绑定事件监听器
bindEventListeners();
};
// 绑定所有事件监听器
const bindEventListeners = () => {
// 关闭报告区域
elements.closeReportBtn.addEventListener('click', handleCloseReport);
// 提交按钮点击事件
elements.submitBtn.addEventListener('click', handleSubmit);
// 下载链接点击事件
elements.reportLink.addEventListener('click', handleDownload);
// 切换防刷新状态
elements.toggleRefreshBtn.addEventListener('click', handleToggleRefresh);
};
// 处理关闭报告
const handleCloseReport = (event) => {
event.preventDefault();
elements.reportArea.style.display = 'none';
};
// 处理切换防刷新状态
const handleToggleRefresh = (event) => {
event.preventDefault();
preventRefresh = !preventRefresh;
if (preventRefresh) {
elements.toggleRefreshBtn.textContent = '禁用防刷新';
elements.toggleRefreshBtn.style.backgroundColor = '#27ae60';
elements.refreshStatus.textContent = '防刷新: 启用';
console.log('防刷新已启用');
} else {
elements.toggleRefreshBtn.textContent = '启用防刷新';
elements.toggleRefreshBtn.style.backgroundColor = '#e74c3c';
elements.refreshStatus.textContent = '防刷新: 禁用';
console.log('防刷新已禁用');
}
};
// 处理提交
const handleSubmit = async (event) => {
console.log('提交按钮被点击');
// 1. 阻止所有默认行为
event.preventDefault();
event.stopPropagation();
console.log('阻止了默认行为和冒泡');
// 2. 防止重复提交
if (isProcessing) {
console.log('正在处理中,忽略重复点击');
return;
}
isProcessing = true;
// 3. 启用全局防刷新拦截器
preventRefresh = true;
console.log('启用全局防刷新拦截器');
console.log('开始处理请求');
// 3. 更新UI状态
updateUIState(true);
try {
// 4. 验证输入
console.log('验证输入...');
const validationError = validateInputs();
if (validationError) {
console.log('输入验证失败:', validationError);
elements.resultPre.textContent = validationError;
return;
}
console.log('输入验证通过');
// 5. 准备数据
console.log('准备表单数据...');
const formData = prepareFormData();
// 6. 发送请求
console.log('发送请求到后端...');
const data = await sendRequest(formData);
console.log('请求成功完成');
// 7. 处理响应
console.log('处理响应数据...');
processResponse(data);
console.log('响应处理完成');
} catch (error) {
// 8. 错误处理
console.log('处理过程中发生错误:', error);
handleError(error);
} finally {
// 9. 恢复UI状态
console.log('恢复UI状态');
updateUIState(false);
isProcessing = false;
// 10. 禁用全局防刷新拦截器
preventRefresh = false;
console.log('禁用全局防刷新拦截器');
}
};
// 更新UI状态
const updateUIState = (isLoading) => {
if (isLoading) {
elements.submitBtn.disabled = true;
elements.submitBtn.textContent = '检查中...';
elements.loader.style.display = 'block';
elements.resultPre.textContent = '正在检查...';
elements.reportArea.style.display = 'none';
} else {
elements.submitBtn.disabled = false;
elements.submitBtn.textContent = '提交检查';
elements.loader.style.display = 'none';
}
};
// 验证输入
const validateInputs = () => {
if (elements.fileInput.files.length === 0) {
return '请上传一个文件。';
}
const tools = getSelectedTools();
if (tools.length === 0) {
return '请至少选择一个工具。';
}
return null;
};
// 获取选中的工具
const getSelectedTools = () => {
const tools = [];
if (elements.banditCheckbox.checked) tools.push('bandit');
if (elements.flake8Checkbox.checked) tools.push('flake8');
if (elements.pylintCheckbox.checked) tools.push('pylint');
return tools;
};
// 准备表单数据
const prepareFormData = () => {
const formData = new FormData();
formData.append('file', elements.fileInput.files[0]);
formData.append('tools', getSelectedTools().join(','));
return formData;
};
// 发送请求
const sendRequest = async (formData) => {
console.log('开始发送fetch请求...');
console.log('请求URL: http://localhost:3000/check');
try {
const response = await fetch('http://localhost:3000/check', {
method: 'POST',
body: formData
});
console.log('收到响应,状态码:', response.status);
if (!response.ok) {
const errorText = await response.text();
console.error('HTTP错误详情:', {
status: response.status,
statusText: response.statusText,
errorText: errorText
});
throw new Error(`HTTP错误! 状态码: ${response.status}\n${errorText}`);
}
const data = await response.json();
console.log('成功解析JSON响应:', data);
return data;
} catch (error) {
console.error('网络请求异常:', error);
// 重新抛出错误,让上层处理
throw error;
}
};
// 处理响应
const processResponse = (data) => {
// 显示结果
elements.resultPre.textContent = JSON.stringify(data, null, 2);
// 显示报告下载区域
if (data.reportUrl) {
const downloadUrl = 'http://localhost:3000' + data.reportUrl;
elements.reportLink.href = downloadUrl;
elements.reportLink.download = `code_report_${Date.now()}.md`;
elements.reportArea.style.display = 'block';
}
};
// 处理错误
const handleError = (error) => {
elements.resultPre.textContent = `错误: ${error.message}`;
console.error('请求失败:', error);
};
// 处理下载
const handleDownload = async (event) => {
event.preventDefault();
event.stopPropagation();
console.log('开始下载报告...');
try {
// 使用fetch API下载文件
const response = await fetch(elements.reportLink.href);
if (!response.ok) {
throw new Error(`下载失败: ${response.status}`);
}
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
// 创建临时下载链接
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = `code_report_${Date.now()}.md`;
document.body.appendChild(a);
a.click();
// 清理临时资源
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
console.log('下载成功');
} catch (error) {
console.error('下载失败:', error);
alert('下载失败: ' + error.message);
}
};
// 公开初始化方法和状态
return {
init,
get isProcessing() { return isProcessing; }
};
})();
// 防刷新拦截器将在App模块内部设置
// 添加页面刷新监听器用于调试将在App模块中设置
// 监听页面加载完成
window.addEventListener('load', function () {
console.log('页面加载完成');
});
// 监听页面显示状态变化
document.addEventListener('visibilitychange', function () {
console.log('页面可见性变化:', document.visibilityState);
});
// 全局错误处理,防止页面重新加载
window.addEventListener('error', function (event) {
console.error('捕获到全局错误:', event.error);
console.error('错误信息:', event.message);
console.error('错误文件:', event.filename);
console.error('错误行号:', event.lineno);
console.error('错误列号:', event.colno);
// 阻止默认的错误处理行为(可能导致页面重新加载)
event.preventDefault();
return false;
});
// 捕获未处理的Promise错误
window.addEventListener('unhandledrejection', function (event) {
console.error('捕获到未处理的Promise错误:', event.reason);
console.error('Promise错误详情:', event);
// 阻止默认行为
event.preventDefault();
});
// 防止意外的页面刷新
window.addEventListener('beforeunload', function (event) {
// 如果正在处理请求,阻止页面刷新
if (typeof App !== 'undefined' && App.isProcessing) {
console.log('阻止页面刷新,因为正在处理请求');
event.preventDefault();
event.returnValue = '正在处理请求,请稍等...';
return '正在处理请求,请稍等...';
}
});
// 初始化应用
document.addEventListener('DOMContentLoaded', function () {
console.log('DOM内容加载完成开始初始化应用');
try {
App.init();
console.log('应用初始化成功');
} catch (error) {
console.error('应用初始化失败:', error);
// 即使初始化失败,也不要重新加载页面
alert('应用初始化失败,请刷新页面重试');
}
});
</script>
</body>
</html>

@ -0,0 +1,347 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>简化的代码质量检查工具</title>
<style>
body {
font-family: 'Microsoft YaHei', sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f8f9fa;
}
.container {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
margin-bottom: 20px;
}
h1 {
color: #2c3e50;
text-align: center;
margin-bottom: 30px;
}
.tool-checkbox {
margin: 15px 0;
padding: 10px;
background-color: #f1f8ff;
border-radius: 4px;
}
.file-upload {
margin: 20px 0;
}
.download-btn {
display: inline-block;
padding: 10px 20px;
background-color: #4CAF50;
color: white;
text-decoration: none;
border-radius: 4px;
font-weight: bold;
transition: background-color 0.3s;
margin-top: 15px;
}
.download-btn:hover {
background-color: #45a049;
}
#report-area {
margin-top: 20px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
display: none;
background-color: #e8f5e9;
}
#result {
background-color: #f5f5f5;
padding: 15px;
border-radius: 5px;
max-height: 400px;
overflow: auto;
white-space: pre-wrap;
font-family: monospace;
margin-top: 20px;
}
.loader {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin: 20px auto;
display: none;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.btn {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
.btn:hover {
background-color: #2980b9;
}
.btn:disabled {
background-color: #95a5a6;
cursor: not-allowed;
}
.debug-panel {
background-color: #f8f9fa;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
margin-top: 20px;
font-family: monospace;
font-size: 12px;
max-height: 200px;
overflow: auto;
}
</style>
</head>
<body>
<div class="container">
<h1>简化的代码质量检查工具</h1>
<div id="toolContainer">
<fieldset>
<legend>选择工具:</legend>
<div class="tool-checkbox">
<input type="checkbox" id="bandit" name="tools" value="bandit" checked>
<label for="bandit">Bandit</label>
</div>
<div class="tool-checkbox">
<input type="checkbox" id="flake8" name="tools" value="flake8" checked>
<label for="flake8">Flake8</label>
</div>
<div class="tool-checkbox">
<input type="checkbox" id="pylint" name="tools" value="pylint" checked>
<label for="pylint">Pylint</label>
</div>
</fieldset>
<div class="file-upload">
<label for="file">上传代码文件:</label>
<input type="file" id="file" name="file" accept=".py" aria-label="选择Python文件">
</div>
<button type="button" id="submitBtn" class="btn">提交检查</button>
</div>
<div class="loader" id="loader"></div>
<div id="report-area" aria-live="polite">
<h2>检查报告</h2>
<p>报告已生成,点击下载:</p>
<a id="report-download" href="#" class="download-btn">下载报告</a>
<button id="close-report" class="btn" style="margin-left: 10px;">关闭</button>
</div>
<h2>结果:</h2>
<pre id="result" role="log" aria-live="polite"></pre>
<div class="debug-panel">
<strong>调试信息:</strong><br>
<div id="debug-log"></div>
</div>
</div>
<script>
// 简单的调试日志函数
function debugLog(message, data = null) {
const timestamp = new Date().toLocaleTimeString();
const logEntry = `[${timestamp}] ${message}`;
if (data) {
console.log(logEntry, data);
} else {
console.log(logEntry);
}
const debugDiv = document.getElementById('debug-log');
debugDiv.innerHTML += logEntry + '<br>';
debugDiv.scrollTop = debugDiv.scrollHeight;
}
// 页面加载
document.addEventListener('DOMContentLoaded', function () {
debugLog('页面DOM加载完成');
const submitBtn = document.getElementById('submitBtn');
const resultPre = document.getElementById('result');
const reportArea = document.getElementById('report-area');
const reportLink = document.getElementById('report-download');
const closeReportBtn = document.getElementById('close-report');
const loader = document.getElementById('loader');
const fileInput = document.getElementById('file');
let isProcessing = false;
// 初始隐藏报告区域
reportArea.style.display = 'none';
// 关闭报告区域
closeReportBtn.addEventListener('click', function (event) {
event.preventDefault();
reportArea.style.display = 'none';
debugLog('报告区域已关闭');
});
// 提交按钮处理
submitBtn.addEventListener('click', async function (event) {
debugLog('提交按钮被点击');
// 阻止所有默认行为
event.preventDefault();
event.stopPropagation();
debugLog('阻止了默认行为和冒泡');
// 防止重复提交
if (isProcessing) {
debugLog('正在处理中,忽略重复点击');
return;
}
isProcessing = true;
debugLog('开始处理请求');
// 更新UI状态
submitBtn.disabled = true;
submitBtn.textContent = '检查中...';
loader.style.display = 'block';
resultPre.textContent = '正在检查...';
reportArea.style.display = 'none';
try {
// 验证文件
if (fileInput.files.length === 0) {
throw new Error('请上传一个文件');
}
debugLog('文件验证通过');
// 获取选中的工具
const tools = [];
if (document.getElementById('bandit').checked) tools.push('bandit');
if (document.getElementById('flake8').checked) tools.push('flake8');
if (document.getElementById('pylint').checked) tools.push('pylint');
if (tools.length === 0) {
throw new Error('请至少选择一个工具');
}
debugLog('工具选择:', tools);
// 准备表单数据
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('tools', tools.join(','));
debugLog('表单数据准备完成');
// 发送请求
debugLog('开始发送网络请求...');
const response = await fetch('http://localhost:3000/check', {
method: 'POST',
body: formData
});
debugLog('收到响应,状态码:', response.status);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`HTTP错误! 状态码: ${response.status}\n${errorText}`);
}
const data = await response.json();
debugLog('成功解析响应数据');
debugLog('响应数据结构:', data);
// 显示结果
resultPre.textContent = JSON.stringify(data, null, 2);
debugLog('结果已显示在页面上');
// 显示报告下载区域
if (data.reportUrl) {
const downloadUrl = 'http://localhost:3000' + data.reportUrl;
reportLink.href = downloadUrl;
reportLink.download = `code_report_${Date.now()}.md`;
reportArea.style.display = 'block';
debugLog('报告下载链接已设置');
}
} catch (error) {
debugLog('处理过程中发生错误:', error.message);
resultPre.textContent = `错误: ${error.message}`;
console.error('详细错误信息:', error);
} finally {
// 恢复UI状态
submitBtn.disabled = false;
submitBtn.textContent = '提交检查';
loader.style.display = 'none';
isProcessing = false;
debugLog('UI状态已恢复');
}
});
// 下载链接处理
reportLink.addEventListener('click', function (event) {
event.preventDefault();
debugLog('下载链接被点击');
// 使用简单的下载方式
const link = document.createElement('a');
link.href = reportLink.href;
link.download = reportLink.download;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
debugLog('下载已启动');
});
debugLog('事件监听器设置完成');
});
// 全局错误处理
window.addEventListener('error', function (event) {
console.error('全局JavaScript错误:', event.error);
debugLog('捕获到全局错误: ' + event.message);
});
window.addEventListener('unhandledrejection', function (event) {
console.error('未处理的Promise错误:', event.reason);
debugLog('捕获到未处理Promise错误: ' + event.reason);
});
debugLog('脚本初始化完成');
</script>
</body>
</html>

@ -0,0 +1,193 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>防刷新测试页面</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.test-section {
margin: 20px 0;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
}
.btn {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
.btn:hover {
background-color: #2980b9;
}
.status {
margin: 10px 0;
padding: 10px;
background-color: #f0f0f0;
border-radius: 4px;
}
</style>
</head>
<body>
<h1>防刷新测试页面</h1>
<div class="test-section">
<h2>防刷新状态控制</h2>
<button id="toggleRefresh" class="btn">启用防刷新</button>
<span id="refreshStatus">防刷新: 禁用</span>
</div>
<div class="test-section">
<h2>测试按钮</h2>
<button id="testBtn1" class="btn">测试按钮1</button>
<button id="testBtn2" class="btn">测试按钮2</button>
<button id="testBtn3" class="btn">测试按钮3</button>
<a id="testLink" href="#" class="btn" style="text-decoration: none;">测试链接</a>
</div>
<div class="test-section">
<h2>结果显示</h2>
<div id="results" class="status">
点击按钮查看结果...
</div>
</div>
<div class="test-section">
<h2>键盘测试</h2>
<p>尝试按 F5 或 Ctrl+R 键测试防刷新功能</p>
<div id="keyboardResults" class="status">
键盘事件将在这里显示...
</div>
</div>
<script>
// 防刷新状态
let preventRefresh = false;
let eventCount = 0;
// DOM元素
const elements = {
toggleRefresh: document.getElementById('toggleRefresh'),
refreshStatus: document.getElementById('refreshStatus'),
testBtn1: document.getElementById('testBtn1'),
testBtn2: document.getElementById('testBtn2'),
testBtn3: document.getElementById('testBtn3'),
testLink: document.getElementById('testLink'),
results: document.getElementById('results'),
keyboardResults: document.getElementById('keyboardResults')
};
// 全局防刷新拦截器
function setupRefreshPrevention() {
// 拦截所有点击事件
document.addEventListener('click', function (event) {
if (preventRefresh) {
eventCount++;
console.log(`拦截到点击事件 #${eventCount}:`, event.target.tagName, event.target.id || event.target.textContent);
elements.results.innerHTML += `<br>点击事件 #${eventCount}: ${event.target.tagName} - ${event.target.id || event.target.textContent}`;
event.preventDefault();
event.stopPropagation();
return false;
}
}, true);
// 拦截键盘事件
document.addEventListener('keydown', function (event) {
if (preventRefresh) {
console.log('拦截到键盘事件:', event.key, event.ctrlKey ? 'Ctrl+' : '');
elements.keyboardResults.innerHTML += `<br>键盘事件: ${event.key}${event.ctrlKey ? ' (Ctrl)' : ''}`;
event.preventDefault();
event.stopPropagation();
return false;
}
}, true);
// 拦截链接点击
document.addEventListener('click', function (event) {
if (preventRefresh && event.target.tagName === 'A') {
console.log('拦截到链接点击:', event.target.href);
event.preventDefault();
event.stopPropagation();
return false;
}
}, true);
}
// 切换防刷新状态
elements.toggleRefresh.addEventListener('click', function (event) {
event.preventDefault();
preventRefresh = !preventRefresh;
if (preventRefresh) {
elements.toggleRefresh.textContent = '禁用防刷新';
elements.toggleRefresh.style.backgroundColor = '#27ae60';
elements.refreshStatus.textContent = '防刷新: 启用';
console.log('防刷新已启用');
} else {
elements.toggleRefresh.textContent = '启用防刷新';
elements.toggleRefresh.style.backgroundColor = '#3498db';
elements.refreshStatus.textContent = '防刷新: 禁用';
eventCount = 0;
elements.results.innerHTML = '点击按钮查看结果...';
elements.keyboardResults.innerHTML = '键盘事件将在这里显示...';
console.log('防刷新已禁用');
}
});
// 添加测试按钮事件
[elements.testBtn1, elements.testBtn2, elements.testBtn3].forEach((btn, index) => {
btn.addEventListener('click', function (event) {
if (!preventRefresh) {
elements.results.innerHTML += `<br>按钮 ${index + 1} 被点击`;
}
});
});
// 添加测试链接事件
elements.testLink.addEventListener('click', function (event) {
if (!preventRefresh) {
elements.results.innerHTML += '<br>链接被点击';
}
});
// 页面事件监听器
window.addEventListener('beforeunload', function (event) {
console.log('页面即将被刷新或关闭');
if (preventRefresh) {
event.preventDefault();
event.returnValue = '';
return '';
}
});
// 初始化
setupRefreshPrevention();
console.log('防刷新测试页面初始化完成');
// 添加页面加载时间戳
const loadTime = new Date().toLocaleString();
console.log('页面加载时间:', loadTime);
document.body.insertAdjacentHTML('afterbegin',
`<div style="background: #e8f5e9; padding: 10px; margin-bottom: 20px; border-radius: 4px;">
页面加载时间: ${loadTime}
</div>`
);
</script>
</body>
</html>

@ -1,169 +0,0 @@
# 系统创新点与差异化优势说明
## 一、与华为CodeArts的差异化分析
### 华为CodeArts的主要特点
- 企业级DevOps平台集成代码检查、CI/CD、项目管理等
- 主要面向云端部署,依赖华为云服务
- 商业产品,需要付费使用
- 功能全面但相对封闭,定制化程度有限
### 本系统的核心创新点
#### 1. **本地AI模型支持与数据安全创新** ⭐⭐⭐
**创新性**:这是本系统最重要的创新点
- **本地AI模型集成**支持Ollama等本地AI服务实现完全离线的代码质量分析
- **数据安全保护**:敏感代码(如军工、金融)无需上传到云端,数据完全本地处理
- **云端/本地无缝切换**通过统一接口实现本地AI和云端AI科大讯飞Spark、OpenAI的灵活切换
- **多AI服务适配**支持多种AI服务提供商不绑定单一厂商
**与CodeArts的差异**
- CodeArts主要依赖云端AI服务数据需要上传到华为云
- 本系统提供本地AI选项满足数据不出域的安全要求
- 特别适合军工、金融等对数据安全要求极高的场景
#### 2. **多工具结果智能去重算法创新** ⭐⭐
**创新性**:算法层面的创新
- **多层次去重机制**
- 精确匹配基于文件路径、行号、规则ID
- 模糊匹配基于Jaccard相似度算法识别语义相似的问题
- 跨工具去重自动识别Pylint、Flake8、Bandit检测到的重复问题
- **智能合并策略**:不仅识别重复,还能智能合并相似问题的分析结果
**与CodeArts的差异**
- CodeArts可能只是简单的结果展示缺乏深度的智能去重
- 本系统的Jaccard相似度算法能够识别语义相似但表述不同的问题
- 减少开发者需要处理的重复问题,提高效率
#### 3. **批量AI分析与上下文感知创新** ⭐⭐
**创新性**AI应用方式的创新
- **批量处理优化**一次性将所有问题发送给AI而不是逐个处理大幅提升分析速度
- **完整代码上下文**AI分析时提供完整文件内容而非仅代码片段提供更准确的上下文感知分析
- **统一分析策略**AI先合并重复问题再统一分析避免重复建议
**与CodeArts的差异**
- 传统方式逐个问题分析,效率低且缺乏全局视角
- 本系统批量分析+完整上下文,提供更准确、更一致的建议
- 减少AI调用次数降低成本
#### 4. **军工代码合规性定制化创新** ⭐
**创新性**:应用场景的创新
- **规则集灵活配置**:支持.pylintrc、setup.cfg、.flake8等多种配置文件格式
- **行业定制化**:特别针对军工领域对代码质量的特殊要求,提供定制化的代码合规性检查
- **规则文件管理**:支持规则文件的上传、编辑、应用和管理
**与CodeArts的差异**
- CodeArts是通用平台难以满足特定行业的特殊合规要求
- 本系统提供灵活的规则定制能力,适应不同行业标准
- 特别适合有严格合规要求的场景
#### 5. **轻量级Web平台与开源特性** ⭐
**创新性**:架构和部署的创新
- **轻量级架构**前后端分离Node.js+Express部署简单
- **开源可定制**:代码开源,可根据需求自由定制和扩展
- **本地部署**:支持完全本地部署,不依赖云服务
**与CodeArts的差异**
- CodeArts是商业产品需要付费定制化受限
- 本系统开源免费,可自由修改和扩展
- 适合中小团队、教育机构、个人开发者使用
---
## 二、回答策略建议
### 回答模板1强调数据安全和本地AI创新
**老师您提到的华为CodeArts确实是一个优秀的企业级平台。但我们的系统在以下几个方面有独特的创新价值**
**首先最重要的是本地AI模型支持。** CodeArts主要依赖云端AI服务数据需要上传到华为云。而我们的系统支持Ollama等本地AI模型可以实现完全离线的代码质量分析。这对于军工、金融等对数据安全要求极高的场景非常重要因为敏感代码可以完全在本地处理数据不出域。
**其次我们实现了云端和本地AI的无缝切换。** 通过统一接口用户可以根据需求选择使用本地AI保证数据安全或云端AI获得更强的分析能力这种灵活性是CodeArts所不具备的。
**第三,我们在多工具结果去重方面有算法创新。** 我们不仅做简单的精确匹配去重还使用Jaccard相似度算法进行模糊匹配能够识别语义相似但表述不同的问题这是传统工具所缺乏的。
**最后,我们的系统是开源轻量级的。** CodeArts是商业产品需要付费且定制化受限。我们的系统开源免费特别适合中小团队、教育机构使用也更容易根据特定行业需求进行定制化开发。
**因此我们的系统不是CodeArts的简单复制而是在数据安全、本地AI支持、智能去重算法等方面有独特创新面向的是不同的应用场景和用户群体。**
---
### 回答模板2强调算法创新和应用场景创新
**老师,我理解您的关注。让我从创新角度来说明我们系统的独特价值:**
**1. 算法层面的创新:**
- **智能去重算法**我们使用Jaccard相似度算法进行模糊匹配能够识别不同工具检测到的语义相似问题这是传统工具简单去重所不具备的
- **批量AI分析策略**:我们不是逐个问题分析,而是批量处理+完整代码上下文让AI有全局视角提供更准确、更一致的建议
**2. 应用场景的创新:**
- **本地AI支持**这是CodeArts等商业平台所不具备的。我们支持Ollama等本地AI满足数据不出域的安全要求特别适合军工、金融等敏感领域
- **行业定制化**我们提供灵活的规则集管理可以针对军工等特定行业的合规要求进行定制而CodeArts作为通用平台难以满足这些特殊需求
**3. 技术架构的创新:**
- **多AI服务适配**通过统一接口支持科大讯飞Spark、OpenAI、本地Ollama等多种AI服务不绑定单一厂商
- **轻量级开源架构**相比CodeArts的商业化、重量级平台我们的系统更轻量、更灵活适合中小团队和教育场景
**因此,我们的创新点不在于"有没有AI",而在于"如何更好地应用AI"和"如何满足特定场景需求"。我们的系统在数据安全、算法优化、场景定制等方面有独特价值。**
---
### 回答模板3从研究价值角度回答
**老师,从学术研究的角度,我们的系统在以下方面有研究价值:**
**1. 多工具协同的智能去重算法研究:**
- 我们提出了基于Jaccard相似度的跨工具问题去重算法这在学术上是有研究价值的
- 如何准确识别不同工具检测到的语义相似问题,是一个值得研究的算法问题
**2. 本地AI与云端AI的统一接口设计**
- 我们设计了一个统一接口能够无缝切换本地AI和云端AI这在架构设计上有创新
- 如何平衡数据安全和AI能力是一个实际应用中的重要问题
**3. 批量AI分析与上下文感知的优化**
- 我们研究了如何通过批量处理和完整上下文来提升AI分析的准确性和效率
- 这是一个AI应用优化的研究问题
**4. 特定领域的代码合规性检查:**
- 我们针对军工等特定领域进行了定制化研究,这是应用层面的创新
- 如何将通用工具适配到特定行业需求,是一个有价值的研究方向
**因此虽然CodeArts在商业应用上很成熟但我们的系统在算法研究、架构设计、应用创新等方面有独特的学术价值。**
---
## 三、关键数据支撑(如果可能,准备一些数据)
1. **去重效果**我们的智能去重算法能够减少X%的重复问题
2. **分析效率**批量AI分析比逐个分析快X倍
3. **数据安全**本地AI模式确保100%的数据不出域
4. **成本对比**:开源免费 vs CodeArts的商业费用
---
## 四、总结
**核心创新点排序(按重要性):**
1. **本地AI模型支持** - 数据安全创新,满足敏感场景需求
2. **智能去重算法** - 算法创新,提升分析效率
3. **批量AI分析** - AI应用优化提升准确性和效率
4. **行业定制化** - 应用场景创新,满足特定需求
5. **开源轻量架构** - 架构创新,降低使用门槛
**与CodeArts的定位差异**
- CodeArts企业级、商业化、云端为主、通用平台
- 本系统开源、轻量级、本地AI支持、行业定制、教育友好
**回答要点:**
- 承认CodeArts的优秀但强调我们的差异化创新
- 重点突出本地AI支持数据安全
- 强调算法创新(智能去重)
- 说明应用场景的不同(军工、教育等)
- 从研究价值角度说明学术贡献

@ -1,217 +0,0 @@
# FortifyCode 快速开始指南
## 一键启动
### Windows 用户
双击运行 `start_server.bat` 文件
### Linux/Mac 用户
```bash
chmod +x start_server.sh
./start_server.sh
```
### 使用 npm
```bash
npm start
```
## 首次使用前的准备
### 1. 安装 Node.js
访问 https://nodejs.org/ 下载并安装 Node.js (推荐 LTS 版本)
验证安装:
```bash
node --version
npm --version
```
### 2. 安装 Python
访问 https://www.python.org/ 下载并安装 Python 3.7+
**重要**: 安装时勾选 "Add Python to PATH"
验证安装:
```bash
python --version
```
### 3. 安装项目依赖
#### Node.js 依赖
```bash
cd src
npm install
```
#### Python 代码检查工具
```bash
pip install -r requirements.txt
```
或手动安装:
```bash
pip install pylint flake8 bandit
```
## 启动服务器
选择以下任一方式:
### 方式 1: 使用启动脚本(推荐)
- Windows: 双击 `start_server.bat`
- Linux/Mac: 运行 `./start_server.sh`
### 方式 2: 使用 npm
```bash
npm start
```
### 方式 3: 直接运行
```bash
node backend.js
```
## 访问系统
启动成功后,浏览器访问:
```
http://localhost:5000
```
你会看到 FortifyCode 的主界面。
## 使用示例
### 示例 1: 快速检查单个文件
1. 准备一个 Python 文件,例如 `test.py`:
```python
import os
def test():
x=1+2
print(x)
test()
```
2. 在主页点击或拖拽文件到上传区域
3. 点击"开始检查"按钮
4. 查看检查结果
### 示例 2: 创建项目
1. 点击顶部导航栏的"项目管理"
2. 点击"新建项目"按钮
3. 填写项目信息:
- 项目名称: 我的第一个项目
- 项目描述: 测试项目
- 来源类型: 选择"文件上传"
4. 选择要上传的文件或文件夹
5. 点击"创建项目"
### 示例 3: 查看项目详情
1. 在项目列表中点击项目卡片
2. 查看项目信息和文件浏览器
3. 点击"运行检查"进行代码检查
4. 在"检查历史"中查看历史记录
## 常见问题快速解决
### ❌ 提示 "未找到 Node.js"
**解决**: 安装 Node.js 并确保添加到系统 PATH
### ❌ 提示 "未找到 Python"
**解决**: 安装 Python 并确保添加到系统 PATH
### ❌ 提示 "pylint 未安装"
**解决**:
```bash
pip install pylint
```
### ❌ 提示 "端口 5000 被占用"
**解决**:
- 方案 1: 关闭占用端口的程序
- 方案 2: 修改端口号
编辑 `backend.js` 第 10 行:
```javascript
const PORT = process.env.PORT || 8080; // 改为其他端口
```
### ❌ 上传文件失败
**解决**:
- 确保文件是 .py, .pyx, .pyi 格式
- 确保文件大小不超过 100MB
- 检查磁盘空间是否充足
## 系统架构
```
┌─────────────┐
│ 浏览器 │ http://localhost:5000
└──────┬──────┘
┌─────────────────────────────┐
│ Express 服务器 (Node.js) │
│ - 提供前端页面 │
│ - 处理API请求 │
│ - 文件管理 │
└──────┬──────────────────────┘
┌────────────────────────────┐
│ 代码检查工具 (Python) │
│ - Pylint (代码质量) │
│ - Flake8 (代码规范) │
│ - Bandit (安全检查) │
└────────────────────────────┘
```
## 功能概览
### 🏠 仪表板
- 查看系统统计信息
- 快捷文件上传和检查
- Agent 状态监控
### 📁 项目管理
- 创建和管理多个项目
- 支持 GitHub/Gitee 克隆
- 支持本地文件上传
### 📝 文件浏览器
- 浏览项目文件
- 在线编辑代码
- 文件上传和管理
### 🔍 代码检查
- 多工具协同检查
- 问题分类显示
- 修复建议提供
### 📊 检查报告
- 详细的问题列表
- 问题统计分析
- 历史记录查看
## 下一步
- 📖 阅读完整的 [README.md](README.md)
- 📚 查看 [API 文档](view/API.md)
- 🔧 了解 [军工软件Python编码指南](../doc/军工软件python编码指南.docx)
## 获取帮助
如果遇到问题:
1. 检查控制台输出的错误信息
2. 查看浏览器开发者工具的 Console 选项卡
3. 参考 README.md 中的"常见问题"部分
---
祝使用愉快! 🚀

@ -1,83 +0,0 @@
# 项目清理说明
## 已删除的文件
### 1. 测试文件(根目录)
- ✅ `test_code_sample.py` - 测试代码样例
- ✅ `test_flake8.js` - Flake8测试脚本
- ✅ `test_path.js` - 路径测试脚本
- ✅ `test_sample.py` - 简单测试样例
### 2. 设计文档
- ✅ `配置中心设计.md` - 配置中心设计文档(功能已实现)
- ✅ `项目集成说明.md` - 项目集成说明文档(功能已实现)
## 保留的文档
### 核心项目文档
- ✅ `README.md` - 项目说明文档
- ✅ `项目功能说明.md` - 项目功能说明
- ✅ `研究背景.md` - 研究背景
- ✅ `快速开始指南.md` - 快速开始指南
- ✅ `创新点说明.md` - 创新点说明(用于答辩)
## 建议进一步清理的内容
### 1. 第三方工具的文档和测试(可选)
以下目录包含第三方工具pylint、flake8、bandit的源码、文档和测试如果不需要可以删除
- `pylint-main/doc/` - Pylint文档目录
- `pylint-main/tests/` - Pylint测试目录
- `flake8-main/flake8-main/docs/` - Flake8文档目录
- `flake8-main/flake8-main/tests/` - Flake8测试目录
- `bandit-main/bandit-main/tests/` - Bandit测试目录
- `bandit-main/bandit-main/examples/` - Bandit示例目录
- `bandit-main/bandit-main/doc/` - Bandit文档目录
**注意**:这些是第三方工具的源码,删除后不影响系统运行,但会减少项目体积。
### 2. 输出文件(可选)
- `out/` 目录下的旧报告文件可以定期清理,保留最新的即可
### 3. 空目录
- `scripts/` 目录为空,可以删除(如果存在)
## 清理后的项目结构
```
src/
├── backend.js # 后端主文件
├── frontend/ # 前端文件
├── server/ # 服务器模块
├── config/ # 配置文件目录
├── projects_data/ # 项目数据
├── out/ # 输出报告
├── rule/ # 规则文件
├── pylint-main/ # Pylint源码仅保留核心代码
├── flake8-main/ # Flake8源码仅保留核心代码
├── bandit-main/ # Bandit源码仅保留核心代码
├── README.md # 项目说明
├── 项目功能说明.md # 功能说明
├── 研究背景.md # 研究背景
├── 快速开始指南.md # 快速开始
├── 创新点说明.md # 创新点说明
└── package.json # 依赖配置
```
## 注意事项
1. **不要删除**
- `pylint-main/pylint/` - Pylint核心代码
- `flake8-main/flake8-main/src/flake8/` - Flake8核心代码
- `bandit-main/bandit-main/bandit/` - Bandit核心代码
- 所有配置文件(`package.json`, `requirements.txt`等)
2. **可以删除**
- 第三方工具的文档和测试目录
- 旧的项目报告文件
- 空的目录
3. **建议保留**
- 所有项目文档README、功能说明等
- 核心代码文件
- 配置文件

@ -1,6 +0,0 @@
# 基于AI智能分析与多工具协同的Python代码质量检查系统
## 研究背景
随着Python在军工、金融等关键领域的广泛应用代码质量检查的重要性日益凸显。传统的静态分析工具如Pylint、Flake8、Bandit虽然能有效检测代码问题但存在工具分散、结果重复、缺乏智能分析和详细修复建议等局限性。同时现有研究多集中在单一工具或单一AI模型的应用缺乏多工具协同与AI智能分析的深度融合。因此本研究旨在构建一个基于AI智能分析与多工具协同的Python代码质量检查系统通过集成Pylint、Flake8、Bandit等主流工具实现统一检查平台利用大语言模型进行智能去重、风险评估和详细修复建议生成并支持本地AI模型Ollama和云端AI服务科大讯飞、OpenAI的灵活切换特别针对军工等特殊领域提供定制化的代码合规性检查方案以提升代码质量、提高开发效率、降低安全风险为代码质量检查领域提供新的理论支撑和实践价值。

@ -1,172 +0,0 @@
# 项目功能说明
## 基于AI智能分析与多工具协同的Python代码质量检查系统
### 核心功能
本项目开发了四个核心功能模块:**多工具协同检查**集成Pylint、Flake8、Bandit三大主流Python代码检查工具实现并行执行和结果统一全面覆盖代码质量、编码规范和安全漏洞检测**AI智能分析模块**利用大语言模型对检查结果进行智能去重基于文件路径、行号、规则ID和消息相似度匹配、风险评估划分高、中、低风险等级并给出0-100分评分和详细修复建议生成包含问题分析、修复步骤、代码示例和最佳实践**AI服务支持**提供灵活的AI服务选择支持云端AI服务科大讯飞Spark、OpenAI和本地AI模型Ollama等通过统一接口实现本地与云端AI的无缝切换满足数据安全和离线使用需求**规则集管理**支持自定义规则集配置(.pylintrc、setup.cfg、.flake8等实现规则文件的上传、编辑、应用和管理满足不同行业和项目的特殊合规性要求。这四个核心功能模块协同工作形成了从代码检查、智能分析到规则定制的完整代码质量检查解决方案。
#### 5. 项目管理
**功能描述**:提供完整的项目生命周期管理,支持项目创建、导入、查看和删除。
**主要特性**
- **项目创建**
- 支持从本地文件夹导入项目
- 支持GitHub/Gitee仓库克隆
- 支持文件上传创建项目
- **项目信息**
- 项目基本信息展示(名称、路径、创建时间等)
- 项目文件树浏览
- 项目检查历史记录
- **项目操作**
- 项目删除
- 项目文件管理
- 项目配置管理
#### 6. 在线代码编辑
**功能描述**提供Web端的代码编辑器支持在线查看、编辑和保存代码文件。
**主要特性**
- **文件浏览**
- 树形文件结构展示
- 文件路径导航
- 文件类型过滤
- **代码编辑**
- 在线代码编辑器
- 语法高亮支持Python
- 代码保存功能
- **问题定位**
- 检查结果一键定位到代码位置
- 高亮显示问题代码行
- 快速跳转到问题文件
#### 7. 检查结果展示
**功能描述**:提供直观、详细的检查结果展示,帮助开发者快速理解问题。
**主要特性**
- **结果分类**
- 按问题类型分类(错误、警告、信息)
- 按风险等级分类(高、中、低风险)
- 按文件分组显示
- **详细信息**
- 问题位置(文件、行号、列号)
- 问题规则和消息
- 风险等级和评分
- AI生成的详细修复建议
- **结果筛选**
- 按类型筛选(全部/错误/警告/信息)
- 按风险等级筛选
- 按文件筛选
#### 8. 报告导出
**功能描述**支持将检查结果导出为Markdown格式的报告文件。
**主要特性**
- **报告生成**
- 自动生成Markdown格式报告
- 包含项目信息、检查摘要、问题详情
- 包含AI分析结果和修复建议
- **报告内容**
- 检查结果统计(错误、警告、信息数量)
- AI分析结果去重数量、风险分布
- 详细问题列表(按文件分组)
- 每个问题的完整信息和修复建议
- **报告下载**
- 一键下载报告文件
- 文件名自动包含时间戳
- 支持本地保存和分享
#### 9. 配置中心
**功能描述**集中管理系统配置包括AI配置和本地模型管理。
**主要特性**
- **AI配置管理**
- 选择使用本地AI或云端AI
- 本地模型选择
- 云端AI服务信息显示
- 配置保存和测试连接
- **本地模型管理**
- 本地模型上传和添加
- 模型信息管理名称、描述、API地址
- 模型列表查看
- 模型连接测试
- 模型删除
#### 10. Web可视化平台
**功能描述**提供友好的Web界面支持所有功能的可视化操作。
**主要特性**
- **仪表板**
- 项目统计信息展示
- 合规率、待修复问题、高危漏洞统计
- 最近项目列表
- 快速开始指南
- **用户界面**
- 响应式设计,适配不同屏幕尺寸
- 现代化UI设计操作直观
- 实时进度显示
- 错误提示和成功提示
- **导航系统**
- 顶部导航栏
- 页面切换
- 功能模块快速访问
### 二、技术特性
#### 1. 系统架构
- **前后端分离**前端使用HTML5/CSS3/JavaScript后端使用Node.js/Express
- **模块化设计**:功能模块独立,便于维护和扩展
- **RESTful API**标准化的API接口设计
#### 2. 性能优化
- **并行检查**:多个工具同时运行,提高检查效率
- **批量处理**AI分析支持批量处理所有问题
- **结果缓存**:检查结果缓存,避免重复计算
#### 3. 错误处理
- **容错机制**:工具执行失败时自动回退
- **错误提示**:友好的错误信息提示
- **日志记录**:详细的日志记录,便于问题排查
#### 4. 安全性
- **路径验证**:防止路径遍历攻击
- **文件类型验证**:限制上传文件类型
- **输入验证**API参数验证和过滤
### 三、应用场景
1. **军工代码合规性检查**:针对军工领域对代码质量的特殊要求,提供定制化的代码合规性检查
2. **企业代码质量管控**:帮助企业建立代码质量检查流程,提升代码质量
3. **开源项目维护**:帮助开源项目维护者快速发现和修复代码问题
4. **教学和培训**:作为代码质量检查的教学工具,帮助学生理解代码规范
5. **CI/CD集成**:可集成到持续集成流程中,自动化代码质量检查
### 四、系统优势
1. **多工具协同**:集成多个工具,覆盖代码质量、风格、安全等各个方面
2. **AI智能分析**利用AI技术提供智能去重、风险评估和详细建议
3. **灵活配置**支持自定义规则集和AI服务选择
4. **用户友好**直观的Web界面操作简单便捷
5. **功能完整**:从项目导入到结果导出,提供完整的代码检查流程
---
*本文档基于"基于AI智能分析与多工具协同的Python代码质量检查系统"项目编写*

@ -1,310 +0,0 @@
# 3.3 用例设计
## 3.3.1 "执行代码检测"用例实现的设计方案
"执行代码检测"功能的实现主要是通过"ToolManager"对象提供的服务调用多个代码质量检测工具Bandit、Flake8、Pylint对用户上传的Python代码文件进行分析从而生成详细的检测报告。具体实现过程见图3.3.1 所描述的用例实现顺序图。
**图3.3.1 "执行代码检测"用例实现的顺序图**
```plantuml
@startuml
title 图3.3.1 "执行代码检测"用例实现的顺序图
actor 用户
participant "<<boundary>>\n<u>DashboardView</u>" as LoginUI
participant "<<controller>>\n<u>BackendService</u>" as LoginManager
participant "<<entity>>\n<u>FileStorageManager</u>" as UserLibrary
participant "<<entity>>\n<u>DetectionTask</u>" as DetectionTask
participant "<<controller>>\n<u>ToolManager</u>" as ToolManager
participant "<<entity>>\n<u>CodeAnalysisEngine</u>" as ExtTool
participant "<<controller>>\n<u>ReportGenerator</u>" as ReportGenerator
用户 -> LoginUI: 1. 输入账号和密码
用户 -> LoginUI: 2. 选择代码文件并上传
用户 -> LoginUI: 3. 选择检测工具
用户 -> LoginUI: 4. 点击"开始检查"按钮
LoginUI -> LoginManager: 5. handleCodeCheck(file, tools)
activate LoginManager
LoginManager -> LoginManager: 6. 验证请求参数
LoginManager -> UserLibrary: 7. saveUploadedFile(file)
activate UserLibrary
UserLibrary -> UserLibrary: 8. isFileValid(file)
UserLibrary --> LoginManager: 9. VerificationResult (文件路径)
deactivate UserLibrary
LoginManager -> DetectionTask: 10. new DetectionTask(filePath, fileName, tools)
activate DetectionTask
DetectionTask -> DetectionTask: 11. 初始化任务属性\n(生成taskId, 设置status=Pending)
DetectionTask --> LoginManager: 12. 返回任务对象
deactivate DetectionTask
LoginManager -> DetectionTask: 13. start()
activate DetectionTask
DetectionTask -> DetectionTask: 14. 设置status=Running\n记录startTime
deactivate DetectionTask
loop 遍历每个选中的工具
LoginManager -> ToolManager: 15. runTool(toolName, filePath)
activate ToolManager
ToolManager -> ToolManager: 16. generateCommand(toolName, filePath)
ToolManager -> ExtTool: 17. executeCommand(command)
activate ExtTool
ExtTool -> ExtTool: 18. 分析代码文件
ExtTool --> ToolManager: 19. 返回原始输出 (stdout)
deactivate ExtTool
ToolManager -> ToolManager: 20. parseToolOutput(toolName, stdout)
ToolManager --> LoginManager: 21. 返回结构化结果
deactivate ToolManager
LoginManager -> DetectionTask: 22. addResult(toolName, result)
activate DetectionTask
DetectionTask -> DetectionTask: 23. 添加结果到results数组
deactivate DetectionTask
end
LoginManager -> ReportGenerator: 24. generateReport(results, filename)
activate ReportGenerator
ReportGenerator -> ReportGenerator: 25. formatToolResult(toolResult)
ReportGenerator --> LoginManager: 26. 返回报告内容
deactivate ReportGenerator
LoginManager -> UserLibrary: 27. saveReport(reportContent, filename)
activate UserLibrary
UserLibrary -> UserLibrary: 28. 保存报告到输出目录
UserLibrary --> LoginManager: 29. 返回报告路径
deactivate UserLibrary
LoginManager -> DetectionTask: 30. complete()
activate DetectionTask
DetectionTask -> DetectionTask: 31. 设置status=Completed\n记录endTime
DetectionTask --> LoginManager: 32. 返回任务结果
deactivate DetectionTask
LoginManager --> LoginUI: 33. CheckResult (检测结果和报告路径)
deactivate LoginManager
LoginUI -> LoginUI: 34. 更新界面显示\n(进度、结果列表)
LoginUI -> 用户: 35. 展示检测结果
@enduml
```
首先,用户通过边界类"DashboardView"对象选择并上传Python代码文件选择要使用的检测工具Bandit、Flake8、Pylint随后该对象向控制类"BackendService"对象发消息"handleCodeCheck(file, tools)",以请求执行代码检测。接收到消息后,"BackendService"对象首先调用实体类"FileStorageManager"对象的方法"saveUploadedFile(file)"以保存上传的文件到临时目录,"FileStorageManager"对象通过自身内部的方法"isFileValid(file)"来判断文件是否有效,并将验证的结果"VerificationResult"返回给"BackendService"对象。
"BackendService"对象收到文件保存成功后,创建实体类"DetectionTask"对象以封装检测任务信息,并调用其"start()"方法将任务状态设置为"Running"。随后,"BackendService"对象遍历用户选中的检测工具,向控制类"ToolManager"对象发消息"runTool(toolName, filePath)"以执行代码检测。"ToolManager"对象通过自身内部的方法"generateCommand(toolName, filePath)"生成命令行命令,并向实体类"CodeAnalysisEngine"对象发消息"executeCommand(command)"以执行代码分析。"CodeAnalysisEngine"对象接收到消息后根据命令类型调用对应的检测工具Bandit、Flake8或Pylint执行代码分析。检测工具完成分析后"CodeAnalysisEngine"对象返回原始输出结果给"ToolManager"对象,"ToolManager"对象通过"parseToolOutput(toolName, stdout)"方法解析原始输出,将其转换为结构化的检测结果,并返回给"BackendService"对象。
"BackendService"对象将每个工具的检测结果添加到"DetectionTask"对象的results数组中。所有工具执行完成后"BackendService"对象向控制类"ReportGenerator"对象发消息"generateReport(results, filename)"以生成检测报告。"ReportGenerator"对象通过自身内部的方法"formatToolResult(toolResult)"格式化每个工具的检测结果生成Markdown格式的报告内容并返回给"BackendService"对象。
随后,"BackendService"对象调用"FileStorageManager"对象的方法"saveReport(reportContent, filename)"保存报告到输出目录,并调用"DetectionTask"对象的"complete()"方法将任务状态设置为"Completed"。"BackendService"对象将包含检测结果和报告路径的"CheckResult"返回给"DashboardView"对象,"DashboardView"对象更新界面显示检测结果列表和统计信息,用户可以在界面上查看详细的检测结果。一旦检测完成,系统将检测结果以列表形式展示给用户,用户可以通过筛选功能查看不同类型的问题(错误、警告、信息)。
## 3.3.2 "创建项目"用例实现的设计方案
"创建项目"功能的实现主要是通过"BackendService"对象提供的服务创建新的代码检查项目记录并根据项目来源类型GitHub、Gitee或文件上传保存相应的项目信息从而使用户能够对不同类型的代码进行分类管理和检查。具体实现过程见图3.3.2 所描述的用例实现顺序图。
**图3.3.2 "创建项目"用例实现的顺序图**
```plantuml
@startuml
title 图3.3.2 "创建项目"用例实现的顺序图
actor 用户
participant "<<boundary>>\n<u>ProjectManagementView</u>" as SettingUI
participant "<<controller>>\n<u>BackendService</u>" as SettingManager
participant "<<entity>>\n<u>FileStorageManager</u>" as FileStorage
participant "<<entity>>\n<u>Project</u>" as Project
用户 -> SettingUI: 1. 点击"新建项目"按钮
SettingUI -> SettingUI: 2. 显示新建项目对话框
用户 -> SettingUI: 3. 输入项目信息\n(名称、描述)
用户 -> SettingUI: 4. 选择项目来源类型
alt 项目来源为GitHub或Gitee
用户 -> SettingUI: 5. 输入Git URL
SettingUI -> SettingManager: 6. startSetting()
activate SettingManager
SettingManager -> SettingManager: 7. 验证项目信息
SettingManager -> Project: 8. new Project(name, description, sourceType, sourceUrl)
activate Project
Project -> Project: 9. 初始化项目属性\n(生成projectId, 设置创建时间)
Project --> SettingManager: 10. 返回项目对象
deactivate Project
SettingManager -> SettingManager: 11. 保存项目记录
SettingManager --> SettingUI: 12. CreateProjectResult (项目ID和基本信息)
deactivate SettingManager
else 项目来源为文件上传
用户 -> SettingUI: 7. 选择文件或文件夹
SettingUI -> SettingManager: 8. startSetting()
activate SettingManager
SettingManager -> SettingManager: 9. 验证项目信息
SettingManager -> Project: 10. new Project(name, description, sourceType)
activate Project
Project -> Project: 11. 初始化项目属性\n(生成projectId, 设置创建时间)
Project --> SettingManager: 12. 返回项目对象
deactivate Project
SettingManager -> SettingManager: 13. 保存项目记录
SettingManager --> SettingUI: 14. CreateProjectResult (项目ID)
deactivate SettingManager
SettingUI -> SettingManager: 15. uploadProjectFiles(projectId, files)
activate SettingManager
loop 遍历每个文件
SettingManager -> FileStorage: 16. saveUploadedFile(file, projectPath)
activate FileStorage
FileStorage -> FileStorage: 17. 保存文件到项目目录
FileStorage --> SettingManager: 18. 返回文件路径
deactivate FileStorage
end
SettingManager --> SettingUI: 19. UploadResult (上传文件数量)
deactivate SettingManager
endif
SettingUI -> SettingUI: 20. 刷新项目列表
SettingUI -> 用户: 21. 显示新创建的项目
@enduml
```
边界类"ProjectManagementView"对象基于用户输入的项目信息(如项目名称、描述、来源类型等),向控制类"BackendService"对象发消息"startSetting()"以进行项目创建的初始化工作;"BackendService"对象接收到消息后,首先验证用户输入的项目信息是否有效,随后创建实体类"Project"对象并初始化项目属性生成projectId、设置创建时间等至此项目创建的初始化工作已经完成。
如果项目来源类型为GitHub或Gitee用户需要输入Git仓库的URL"BackendService"对象将Git URL信息保存到"Project"对象中,并返回创建结果给"ProjectManagementView"对象。如果项目来源类型为文件上传,用户需要选择本地文件或文件夹,"BackendService"对象创建项目记录后,"ProjectManagementView"对象通过消息"uploadProjectFiles(projectId, files)"将文件上传请求发送给"BackendService"对象,随后"BackendService"对象将文件上传请求转发给实体类"FileStorageManager"对象,请求保存文件到项目的专用目录。"FileStorageManager"对象接收到文件后,将文件保存到对应项目的目录中,并返回文件保存结果。
一旦项目创建任务完成,"BackendService"对象将通过消息"CreateProjectResult"将项目创建结果信息反馈给边界类"ProjectManagementView"对象,"ProjectManagementView"对象刷新项目列表并显示新创建的项目,用户可以在项目管理界面中看到新创建的项目。
## 3.3.3 "查看检测结果"用例实现的设计方案
根据用户选择要查看的检测记录,"ProjectDetailView"对象将向"BackendService"对象请求加载检测结果详情,"BackendService"对象从"DetectionTask"对象中获取存储的检测结果,并通过"ReportGenerator"对象格式化结果数据最终将详细的检测结果展示给用户具体实现过程见图3.3.3 所描述的用例实现顺序图。
**图3.3.3 "查看检测结果"用例实现的顺序图**
```plantuml
@startuml
title 图3.3.3 "查看检测结果"用例实现的顺序图
actor 用户
participant "<<boundary>>\n<u>ProjectDetailView</u>" as SettingUI
participant "<<controller>>\n<u>BackendService</u>" as SettingManager
participant "<<entity>>\n<u>DetectionTask</u>" as DetectionTask
participant "<<controller>>\n<u>ReportGenerator</u>" as ReportGenerator
participant "<<entity>>\n<u>FileStorageManager</u>" as FileStorage
用户 -> SettingUI: 1. 进入检测结果查看界面
SettingUI -> SettingManager: 2. getCheckHistory(projectId)
activate SettingManager
SettingManager -> SettingManager: 3. 查询检测历史记录
SettingManager --> SettingUI: 4. CheckHistoryList (检测历史列表)
deactivate SettingManager
SettingUI -> SettingUI: 5. 显示检测历史列表
用户 -> SettingUI: 6. 选择要查看的检测记录
SettingUI -> SettingManager: 7. getCheckResult(projectId, checkId)
activate SettingManager
SettingManager -> DetectionTask: 8. getResults(checkId)
activate DetectionTask
DetectionTask -> DetectionTask: 9. 查询检测结果数据
DetectionTask --> SettingManager: 10. 返回检测结果
deactivate DetectionTask
SettingManager -> ReportGenerator: 11. formatResults(results)
activate ReportGenerator
ReportGenerator -> ReportGenerator: 12. 格式化结果数据\n(按类型分类、排序)
ReportGenerator --> SettingManager: 13. 返回格式化后的结果
deactivate ReportGenerator
SettingManager --> SettingUI: 14. CheckResult (问题列表、统计信息)
deactivate SettingManager
SettingUI -> SettingUI: 15. 显示检测结果摘要\n(错误数、警告数等)
SettingUI -> SettingUI: 16. 显示问题列表
用户 -> SettingUI: 17. 选择筛选类型\n(全部/错误/警告/信息)
SettingUI -> SettingUI: 18. 根据类型筛选问题列表
用户 -> SettingUI: 19. 点击问题查看详情
SettingUI -> SettingUI: 20. 显示问题详情和修复建议
用户 -> SettingUI: 21. 点击下载报告按钮
SettingUI -> SettingManager: 22. downloadReport(reportId)
activate SettingManager
SettingManager -> FileStorage: 23. readFile(reportPath)
activate FileStorage
FileStorage -> FileStorage: 24. 读取报告文件内容
FileStorage --> SettingManager: 25. 返回报告内容
deactivate FileStorage
SettingManager --> SettingUI: 26. 返回报告文件
deactivate SettingManager
SettingUI -> 用户: 27. 触发文件下载
@enduml
```
边界类"ProjectDetailView"对象首先向控制类"BackendService"对象发消息"getCheckHistory(projectId)"以获取项目的检测历史记录列表,"BackendService"对象查询数据库中存储的检测任务记录,并将检测历史列表"CheckHistoryList"返回给"ProjectDetailView"对象,"ProjectDetailView"对象在界面上显示检测历史列表供用户选择。
用户选择要查看的检测记录后,"ProjectDetailView"对象向"BackendService"对象发消息"getCheckResult(projectId, checkId)"以请求加载详细的检测结果。"BackendService"对象接收到消息后,向实体类"DetectionTask"对象发消息"getResults(checkId)"以获取存储的检测结果数据。"DetectionTask"对象从其results属性中查询对应的检测结果并将结果返回给"BackendService"对象。
"BackendService"对象接收到检测结果后,向控制类"ReportGenerator"对象发消息"formatResults(results)"以格式化结果数据。"ReportGenerator"对象通过自身内部的方法对检测结果进行格式化处理,包括按问题类型(错误、警告、信息)进行分类、按严重程度排序等,并将格式化后的结果返回给"BackendService"对象。
"BackendService"对象将包含问题列表和统计信息的"CheckResult"返回给"ProjectDetailView"对象,"ProjectDetailView"对象在界面上显示检测结果摘要(错误数、警告数、信息数等)和详细的问题列表。用户可以通过筛选功能选择查看特定类型的问题,"ProjectDetailView"对象根据用户选择的筛选类型过滤问题列表并更新显示。
如果用户需要下载检测报告,"ProjectDetailView"对象向"BackendService"对象发消息"downloadReport(reportId)"以请求下载报告文件。"BackendService"对象调用实体类"FileStorageManager"对象的方法"readFile(reportPath)"读取报告文件内容,并将报告文件返回给"ProjectDetailView"对象,"ProjectDetailView"对象触发浏览器下载文件,用户可以将检测报告保存到本地。
## 3.3.4 "上传规则文件"用例实现的设计方案
"上传规则文件"功能的实现主要是通过"BackendService"对象提供的服务接收用户上传的规则配置文件验证文件格式的有效性并将文件保存到规则集目录中从而使用户能够自定义代码检测规则以满足不同项目或团队的要求。具体实现过程见图3.3.4 所描述的用例实现顺序图。
**图3.3.4 "上传规则文件"用例实现的顺序图**
```plantuml
@startuml
title 图3.3.4 "上传规则文件"用例实现的顺序图
actor 用户
participant "<<boundary>>\n<u>RuleSetManagementView</u>" as SettingUI
participant "<<controller>>\n<u>BackendService</u>" as SettingManager
participant "<<entity>>\n<u>FileStorageManager</u>" as FileStorage
participant "<<entity>>\n<u>RuleSet</u>" as RuleSet
用户 -> SettingUI: 1. 点击"上传规则文件"按钮
SettingUI -> SettingUI: 2. 打开文件选择对话框
用户 -> SettingUI: 3. 选择规则文件\n(如setup.cfg)
用户 -> SettingUI: 4. 选择规则类型\n(Pylint/Flake8)
SettingUI -> SettingManager: 5. uploadRuleFile(file, ruleType)
activate SettingManager
SettingManager -> SettingManager: 6. 验证文件格式
SettingManager -> SettingManager: 7. isFileValid(file, ruleType)
alt 文件格式有效
SettingManager -> FileStorage: 8. saveRuleFile(file, ruleType)
activate FileStorage
FileStorage -> FileStorage: 9. 保存文件到规则集目录
FileStorage --> SettingManager: 10. 返回文件路径
deactivate FileStorage
SettingManager -> RuleSet: 11. new RuleSet(fileName, filePath, ruleType)
activate RuleSet
RuleSet -> RuleSet: 12. 初始化规则文件属性\n(生成ruleId, 设置更新时间)
RuleSet --> SettingManager: 13. 返回规则对象
deactivate RuleSet
SettingManager -> SettingManager: 14. 保存规则文件元数据
SettingManager --> SettingUI: 15. UploadRuleResult (规则ID和基本信息)
else 文件格式无效
SettingManager --> SettingUI: 16. UploadRuleResult (错误信息)
endif
deactivate SettingManager
SettingUI -> SettingUI: 17. 刷新规则文件列表
SettingUI -> 用户: 18. 显示新上传的规则文件
@enduml
```
边界类"RuleSetManagementView"对象基于用户选择的规则文件和规则类型如Pylint的.pylintrc文件或Flake8的setup.cfg文件向控制类"BackendService"对象发消息"uploadRuleFile(file, ruleType)"以请求上传规则文件;"BackendService"对象接收到消息后,首先通过自身内部的方法"isFileValid(file, ruleType)"验证文件格式是否符合对应的规则类型要求,如果文件格式有效,则向实体类"FileStorageManager"对象发消息"saveRuleFile(file, ruleType)"以保存规则文件到规则集目录。
"FileStorageManager"对象接收到消息后将规则文件保存到对应的规则集目录中如Pylint规则文件保存到pylint_rules目录Flake8规则文件保存到flake8_rules目录并返回文件保存路径给"BackendService"对象。"BackendService"对象收到文件保存成功后,创建实体类"RuleSet"对象以封装规则文件信息,调用其构造函数"new RuleSet(fileName, filePath, ruleType)"初始化规则文件属性生成ruleId、记录文件大小、设置更新时间等并将规则文件元数据保存到系统中。
一旦规则文件上传任务完成,"BackendService"对象将通过消息"UploadRuleResult"将上传结果信息反馈给边界类"RuleSetManagementView"对象如果上传成功则返回规则ID和基本信息如果上传失败则返回错误信息。"RuleSetManagementView"对象刷新规则文件列表并显示新上传的规则文件,用户可以在规则集管理界面中看到新上传的规则文件,并可以对其进行编辑、下载或删除操作。上传的规则文件可以在后续的代码检测中使用,"ToolManager"对象在执行检测时会自动读取并使用相应的规则配置文件。

@ -1,525 +0,0 @@
# 3.4 类设计
## 1精化类间的关系
在分析类图中有一组分析类BackendService、ToolManager、ReportGenerator、FileStorageManager、DetectionTask。在软件设计阶段这些类仍然有意义将成为软件设计模型中的关键设计类。针对这些设计类间关系的精化设计描述如下具体见图3.4.1。
BackendService 类负责协调整个代码检测流程。在具体实现时BackendService 类通过调用 ToolManager 执行代码检测,通过 FileStorageManager 管理文件存储,通过 ReportGenerator 生成检测报告,因而 BackendService 类与 ToolManager、FileStorageManager、ReportGenerator 类之间的语义关系表现为一般的关联关系。DetectionTask 是封装检测任务信息的实体类,因而 BackendService 与 DetectionTask 之间的语义关系表现为组合关系。
BackendService 类需要创建和管理一个或者多个 DetectionTask 类对象,每个 DetectionTask 类对象只能交由一个 BackendService 类对象进行处理,因而 BackendService 类与 DetectionTask 类之间存在一对多的关系。
**图3.4.1 精化核心业务类间以及它们与"BackendService"类间的关系**
```plantuml
@startuml
title 图3.4.1 精化核心业务类间以及它们与"BackendService"类间的关系
class BackendService {
- app: Express
- PORT: Number
- upload: Multer
- TOOL_CONFIG: Object
+ constructor()
+ start()
+ handleHealthCheck(req, res)
+ handleCodeCheck(req, res)
}
class ToolManager {
- toolConfig: Object
- results: Array
+ constructor()
+ runTool(toolName, filePath): Promise<Object>
+ parseToolOutput(toolName, stdout): Object
}
class ReportGenerator {
- reportTemplate: String
- outputDir: String
+ constructor(outputDir)
+ generateReport(results, filename): String
+ saveReport(reportContent, filename): String
}
class FileStorageManager {
- tempDir: String
- outputDir: String
- uploadConfig: Object
+ constructor(tempDir, outputDir)
+ saveUploadedFile(file): String
+ readFile(filePath): String
+ saveReport(reportContent, filename): void
}
class DetectionTask {
- taskId: String
- status: String
- filePath: String
- fileName: String
- selectedTools: Array
- results: Array
+ constructor(filePath, fileName, selectedTools)
+ start(): void
+ addResult(toolName, result): void
+ complete(): void
+ fail(error): void
}
BackendService "1" -- "1" ToolManager : 关联 (调用)
BackendService "1" -- "1" FileStorageManager : 关联 (调用)
BackendService "1" -- "1" ReportGenerator : 关联 (调用)
BackendService "1" -- "*" DetectionTask : 组合 (创建和管理)
@enduml
```
## 2精化用户界面类间的关系
根据§3.2 节的用户界面设计本系统在Web端包含 DashboardView、ProjectManagementView、ProjectDetailView 和 RuleSetManagementView 等一组界面以支持用户的操作。显然这些用户界面类之间具有如图3.4.2 所描述的关联关系。
**图3.4.2 精化用户界面类间的关系**
```plantuml
@startuml
title 图3.4.2 精化用户界面类间的关系
class DashboardView {
+ displayStats()
+ handleFileUpload()
+ startCheck()
+ stopCheck()
}
class ProjectManagementView {
+ displayProjectList()
+ createProject()
+ searchProject()
+ deleteProject()
}
class ProjectDetailView {
+ displayProjectInfo()
+ displayCheckHistory()
+ viewCodeFile()
+ viewCheckResult()
}
class RuleSetManagementView {
+ uploadRuleFile()
+ displayRuleList()
+ editRule()
+ deleteRule()
}
DashboardView --> ProjectManagementView : 导航到
DashboardView --> RuleSetManagementView : 导航到
ProjectManagementView --> ProjectDetailView : 查看详情
ProjectDetailView --> DashboardView : 返回
@enduml
```
## 3精化关键设计类间的关系
根据§3.3 节的用例设计,"执行代码检测"用例是本系统的核心用例。该用例涉及 BackendService、FileStorageManager、ToolManager、ReportGenerator 和 DetectionTask 五个关键设计类。根据§3.3 所描述的顺序图中五个类间的交互, BackendService 类与 FileStorageManager 类之间具有直接关联关系BackendService 类与 ToolManager 类之间具有直接关联关系ToolManager 类与 ReportGenerator 类之间是依赖关系这些类的关系精化如图3.4.3所示。
**图3.4.3 精化"执行代码检测"用例中类间的关系**
```plantuml
@startuml
title 图3.4.3 精化"执行代码检测"用例中类间的关系
class BackendService {
+ handleCodeCheck(req, res)
}
class FileStorageManager {
+ saveUploadedFile(file): String
+ saveReport(reportContent, filename): void
}
class ToolManager {
+ runTool(toolName, filePath): Promise<Object>
+ parseToolOutput(toolName, stdout): Object
}
class ReportGenerator {
+ generateReport(results, filename): String
}
class DetectionTask {
+ constructor(filePath, fileName, selectedTools)
+ start(): void
+ addResult(toolName, result): void
+ complete(): void
}
BackendService "1" -- "1" FileStorageManager : 关联 (保存文件)
BackendService "1" -- "1" ToolManager : 关联 (调度工具)
BackendService "1" -- "1" DetectionTask : 关联 (创建/管理任务)
ToolManager ..> ReportGenerator : 依赖 (生成报告)
FileStorageManager ..> DetectionTask : 依赖 (存储任务相关文件)
@enduml
```
## 4精化DetectionTask 类属性的设计
DetectionTask 类属性的精化设计描述如下。
1有六项基本属性任务ID"`taskId`"、文件路径"`filePath`"、文件名"`fileName`"、选中的工具"`selectedTools`"、任务状态"`status`"和检测结果"`results`"。它们的类型分别为`String`、`String`、`String`、`Array`、`String`和`Array`。
2这些属性是任务的核心标识和状态信息对外部其他类不可见这六项属性的可见范围为"`private`"。
3`taskId` 在构造时自动生成,`status` 初始值为`"Pending"``results` 初始值为空数组,其他属性在构造时传入。
## 5精化DashboardView 类属性的设计
在用户界面设计中,有"DashboardView"界面用于支持用户上传代码文件并启动代码检测具体见图3.2.1 所示。"DashboardView"界面类属性的精化设计描述如下。
-有一组属性分别对应于界面中的静态元素、用户输入元素和命令界面元素,具体包括:"logo"为界面标识图标,其类型为静态元素,如图标;"uploadArea"为文件上传区域,其类型为用户输入元素,如文件选择框;"fileInput"为用户选择的文件,其类型为用户输入元素,如文件输入框;"startCheckBtn"旨在开始代码检测,其类型为命令界面元素,如按钮;"stopCheckBtn"旨在停止代码检测,其类型为命令界面元素,如按钮;"progressContainer"旨在显示检测进度,其类型为静态元素,如进度条;"resultsSection"旨在显示检测结果,其类型为静态元素,如结果列表。
-这些属性对外部其他类均不可见,它们的可见范围设置为"`private`"。
-"startCheckBtn"和"stopCheckBtn"二个属性的初始值不为空,需要将其预先设置为相应界面的按钮元素。
## 6精化ToolManager 类属性的设计
ToolManager 是"代码检测服务"子系统中的一个重要控制类。根据该子系统中用例实现的交互图ToolManager 类至少有二项基本属性:"toolConfig"以表示工具配置映射表、"results"以表示工具执行结果集合。具体的ToolManager 类属性的精化设计描述如下。
-`private Object toolConfig`,表示工具配置映射表,包含每个工具的命令、参数生成函数和结果解析器
-`private Array results`,表示工具执行结果集合,用于临时存储各工具的原始输出
-这些属性对外部其他类均不可见,它们的可见范围设置为"`private`"。
-`toolConfig` 在构造时根据预设配置初始化,`results` 初始值为空数组。
## 7精化"代码检测服务"子系统中部分类方法的设计
根据控制类 BackendService 的职责,它有一个 `public` 方法 `handleCodeCheck(req, res)` 用于处理代码检测请求。此外,为了在代码检测之前检查上传文件的合法性,它有一个 `private` 方法 `validateFile(req)`,专门用于判断上传的文件是否满足相关的规范和要求。
该方法的主要功能是要依据用户上传的文件和选中的工具判断是否可以启动检测任务,为此可以设计一个 `private` 的方法 `validateRequest(req)`,专门用于判断请求的合法性。
ToolManager 实体类负责管理系统中的代码检测工具,它有一系列的 `public` 方法以实现对工具的管理,包括 `runTool(toolName, filePath)` 方法实现执行指定的代码检测工具,`parseToolOutput(toolName, stdout)` 方法实现解析工具的原始输出,`getAvailableTools()` 方法实现获取所有可用的工具列表,`validateTool(toolName)` 方法以判断工具名称是否有效。此外ToolManager 类还具有创建方法 `constructor()` 和初始化方法 `initToolConfig()` 以便建立工具配置。
**图3.4.4 精化设计BackendService、ToolManager、ReportGenerator 等类的方法**
```plantuml
@startuml
title 图3.4.4 精化设计BackendService、ToolManager、ReportGenerator 等类的方法
class BackendService {
- app: Express
- PORT: Number = 3000
- upload: Multer
- TOOL_CONFIG: Object
+ constructor()
- configureCORS()
- configureUpload()
- setupRoutes()
+ start()
+ handleHealthCheck(req, res)
+ handleCodeCheck(req, res)
- validateFile(req): Boolean
- validateRequest(req): Boolean
}
class ToolManager {
- toolConfig: Object
- results: Array
+ constructor()
+ initToolConfig()
+ runTool(toolName, filePath): Promise<Object>
+ parseToolOutput(toolName, stdout): Object
+ getAvailableTools(): Array
+ validateTool(toolName): Boolean
}
class ReportGenerator {
- reportTemplate: String
- outputDir: String
+ constructor(outputDir)
+ generateReport(results, filename): String
+ saveReport(reportContent, filename): String
- formatToolResult(toolResult): String
- ensureOutputDir()
}
class FileStorageManager {
- tempDir: String
- outputDir: String
- uploadConfig: Object
+ constructor(tempDir, outputDir)
+ saveUploadedFile(file): String
+ readFile(filePath): String
+ saveReport(reportContent, filename): void
+ deleteTempFile(filePath): void
- ensureDirectory(dirPath)
}
@enduml
```
## 8精化"代码检测服务"子系统CodeCheckService中部分类方法的设计
**图3.4.5 "CodeCheckService"子系统的设计类图**
图3.4.5 描述了"代码检测服务"子系统中实现代码检测的一组对象类。根据该子系统的用例实现交互图,可以对子系统中 ToolManager 和 DetectionTask 两个类的方法进行精化设计。首先,针对 DetectionTask 类的六个属性,分别设计相关的方法以设置和获取六个属性的取值;其次,针对 ToolManager 类的方法,详细设计其参数信息。
-`public void setTaskId(String taskId)`,设置任务的唯一标识符
-`public void setStatus(String status)`,设置任务的当前状态
-`public void setFilePath(String filePath)`,设置待检测文件的路径
-`public void setFileName(String fileName)`,设置原始文件名
-`public void setSelectedTools(Array selectedTools)`,设置选中的检测工具列表
-`public void addResult(String toolName, Object result)`,添加工具的检测结果
-`public String getTaskId()`,获取任务的唯一标识符
-`public String getStatus()`,获取任务的当前状态
-`public String getFilePath()`,获取待检测文件的路径
-`public String getFileName()`,获取原始文件名
-`public Array getSelectedTools()`,获取选中的检测工具列表
-`public Array getResults()`,获取所有检测结果
-ToolManager 类的方法 `runTool(toolName, filePath)` 精化如下: `public Promise<Object> runTool(String toolName, String filePath, Object config)`
```plantuml
@startuml
title 图3.4.5 "CodeCheckService"子系统的设计类图
class DetectionTask {
- taskId: String
- status: String
- filePath: String
- fileName: String
- selectedTools: Array
- results: Array
+ constructor(filePath, fileName, selectedTools)
+ setTaskId(taskId)
+ setStatus(status)
+ setFilePath(filePath)
+ setFileName(fileName)
+ setSelectedTools(selectedTools)
+ addResult(toolName, result)
+ getTaskId(): String
+ getStatus(): String
+ getFilePath(): String
+ getFileName(): String
+ getSelectedTools(): Array
+ getResults(): Array
}
class ToolManager {
- toolConfig: Object
- results: Array
+ constructor()
+ runTool(toolName, filePath, config): Promise<Object>
+ parseToolOutput(toolName, stdout): Object
}
DetectionTask --> ToolManager : 调用
@enduml
```
## 9精化BackendService 类中"handleCodeCheck()"方法的实现算法设计
图3.4.6 用UML 活动图描述了 BackendService 类中 `handleCodeCheck()` 方法的精化设计,它定义了该方法的接口 `public handleCodeCheck(req, res)`,描述了其内部的实现算法:首先判断上传文件和检测参数二个输入的参数是否为空,如果为空则返回错误;如果不为空,则调用 FileStorageManager 对象的方法 `saveUploadedFile()` 以保存上传的文件,如果保存成功则创建 DetectionTask 对象并调用 ToolManager 对象的方法 `runTool()` 以执行代码检测,如果检测成功则调用 ReportGenerator 对象的方法 `generateReport()` 以生成检测报告,否则返回检测失败。
**图3.4.6 精化BackendService 类中handleCodeCheck()方法的详细设计**
```plantuml
@startuml
title 图3.4.6 精化BackendService 类中handleCodeCheck()方法的详细设计
start
:接收HTTP请求 (req, res);
:获取上传文件和检测参数;
if (文件为空?) then (是)
:返回错误响应 (400 Bad Request);
stop
else (否)
if (检测参数为空?) then (是)
:返回错误响应 (400 Bad Request);
stop
else (否)
:调用FileStorageManager.saveUploadedFile()保存文件;
if (文件保存成功?) then (是)
:创建DetectionTask对象;
:调用DetectionTask.start()启动任务;
:调用ToolManager.runTool()执行检测;
if (检测执行成功?) then (是)
:收集所有工具的检测结果;
:调用ReportGenerator.generateReport()生成报告;
:调用FileStorageManager.saveReport()保存报告;
:更新DetectionTask状态为Completed;
:返回成功响应 (报告路径);
else (否)
:更新DetectionTask状态为Failed;
:返回失败响应 (错误信息);
endif
else (否)
:返回文件保存失败响应;
endif
endif
endif
stop
@enduml
```
## 10精化ToolManager 类中"runTool()"方法的实现算法设计
图3.4.7用UML 的活动图描述了 ToolManager 类中 `runTool()` 方法的详细设计,它定义了该方法的接口 `public Promise<Object> runTool(toolName, filePath)`,描述了其内部的实现算法:首先根据工具名称获取工具配置,如果找不到配置则返回错误;如果找到配置,则根据工具类型和文件路径生成命令行命令,接着执行外部命令行工具进行检测,在命令执行过程中捕获标准输出和标准错误,并在命令执行完成后解析原始输出,如果解析成功则返回结构化结果,否则返回解析错误。
**图3.4.7 精化ToolManager 类中runTool()方法的详细设计**
```plantuml
@startuml
title 图3.4.7 精化ToolManager 类中runTool()方法的详细设计
start
:接收工具名称 (toolName) 和文件路径 (filePath);
:根据toolName获取工具配置 (toolConfig);
if (找到工具配置?) then (否)
:返回错误 ("不支持的工具");
stop
else (是)
:根据工具配置生成命令行命令;
if (工具是flake8?) then (是)
:创建本地文件副本;
:使用本地文件路径;
endif
:执行外部命令行工具;
:捕获标准输出和标准错误;
if (命令执行成功?) then (是)
:调用parseToolOutput()解析原始输出;
if (解析成功?) then (是)
:构造结构化结果对象;
:返回检测结果;
else (否)
:返回解析失败错误;
endif
else (否)
:返回工具执行失败错误;
endif
endif
stop
@enduml
```
## 11构造类的状态图和活动图
### 图3.4.8 DetectionTask 类对象的状态图
如果一个类的对象具有较为复杂的状态在其生命周期中需要针对外部和内部事件实施一系列的活动以变迁其状态那么可以考虑构造和绘制类的状态图。DetectionTask 类对象具有较为复杂的状态,它创建时将处于空闲状态"Pending",一旦开始执行代码检测时将处于运行状态"Running"以执行检测工具,如果所有工具执行成功则进入到完成状态"Completed",如果检测失败则进入到失败状态"Failed"图3.4.8 描述了 DetectionTask 类对象的状态图。
**图3.4.8 DetectionTask 类对象的状态图**
```plantuml
@startuml
title 图3.4.8 DetectionTask 类对象的状态图
state "Pending\n(待处理)" as Pending
state "Running\n(运行中)" as Running
state "Completed\n(已完成)" as Completed
state "Failed\n(失败)" as Failed
[*] --> Pending : 创建任务
Pending --> Running : start()\n开始检测
Running --> Completed : complete()\n所有工具执行成功
Running --> Failed : fail()\n检测失败
Completed --> [*] : 任务归档
Failed --> [*] : 任务归档
@enduml
```
### 图3.4.9 用户代码检测流程的活动图
如果某个类在实现其职责过程中需要执行一系列的方法、与其他的对象进行诸多的交互那么可以考虑构造和绘制针对该类某些职责的活动图。图3.4.9用UML 的活动图及泳道机制描述了 DashboardView、BackendService、FileStorageManager、ToolManager、ReportGenerator 和 DetectionTask 六个类对象之间如何通过交互和协作来实现用户代码检测的功能。
**图3.4.9 用户代码检测流程的活动图**
```plantuml
@startuml
title 图3.4.9 用户代码检测流程的活动图
|用户|
start
:上传代码文件;
:选择检测工具;
:点击"开始检查"按钮;
|DashboardView|
:收集文件和参数;
:发送检测请求 (HTTP POST);
|BackendService|
:接收HTTP请求;
:调用FileStorageManager.saveUploadedFile();
|FileStorageManager|
:保存文件到本地临时目录;
:返回文件路径;
|BackendService|
:创建DetectionTask对象;
:设置任务状态为Pending;
:调用DetectionTask.start();
:更新任务状态为Running;
|BackendService|
:遍历选中的工具;
while (还有工具未执行?) is (是)
|ToolManager|
:调用ToolManager.runTool();
:执行外部命令行工具;
:解析工具输出;
:返回结构化结果;
|BackendService|
:调用DetectionTask.addResult();
:添加工具检测结果;
endwhile (否)
|BackendService|
:调用ReportGenerator.generateReport();
|ReportGenerator|
:生成Markdown格式报告;
:返回报告内容;
|BackendService|
:调用FileStorageManager.saveReport();
|FileStorageManager|
:保存报告到输出目录;
:返回报告路径;
|BackendService|
:调用DetectionTask.complete();
:更新任务状态为Completed;
:返回检测结果和报告路径;
|DashboardView|
:接收检测结果;
:更新界面显示 (进度、结果列表);
:提供下载报告链接;
|用户|
:查看检测报告;
stop
@enduml
```
Loading…
Cancel
Save