forked from pu8crm6xf/analysiscode
parent
55d117d840
commit
5562caab93
@ -0,0 +1,51 @@
|
||||
# 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
|
||||
|
||||
@ -1,235 +0,0 @@
|
||||
|
||||
# 基于多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** - 让代码更安全,让开发更放心 🛡️
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -1,30 +0,0 @@
|
||||
多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>
|
||||
@ -1,70 +0,0 @@
|
||||
-- 设置字符集
|
||||
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;
|
||||
|
||||
|
Before Width: | Height: | Size: 5.3 KiB |
@ -1,92 +0,0 @@
|
||||
-- 创建数据库
|
||||
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;
|
||||
@ -1,5 +0,0 @@
|
||||
Flask==2.0.1
|
||||
Flask-Cors==3.0.10
|
||||
PyMySQL==1.0.2
|
||||
python-dotenv==0.19.0
|
||||
Werkzeug==2.0.1
|
||||
@ -1,165 +0,0 @@
|
||||
<!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>
|
||||
@ -1,264 +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>党费管理系统 - 管理后台</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>
|
||||
@ -1,174 +0,0 @@
|
||||
<!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>
|
||||
@ -1,154 +0,0 @@
|
||||
<!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>
|
||||
@ -1,259 +0,0 @@
|
||||
<!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> </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>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,278 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,5 +0,0 @@
|
||||
Flask==2.3.3
|
||||
Flask-CORS==4.0.0
|
||||
PyMySQL==1.1.0
|
||||
GitPython==3.1.40
|
||||
Werkzeug==2.3.7
|
||||
@ -0,0 +1,751 @@
|
||||
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 || 5000;
|
||||
|
||||
// 配置CORS
|
||||
app.use(cors({
|
||||
origin: '*',
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization'],
|
||||
credentials: true
|
||||
}));
|
||||
|
||||
// 解析JSON请求体
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// 请求日志中间件
|
||||
app.use((req, res, next) => {
|
||||
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
|
||||
next();
|
||||
});
|
||||
|
||||
// 配置Multer文件上传
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
const tempDir = path.join(os.tmpdir(), 'fortifycode_uploads');
|
||||
if (!fs.existsSync(tempDir)) {
|
||||
fs.mkdirSync(tempDir, { recursive: true });
|
||||
}
|
||||
cb(null, tempDir);
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
cb(null, `${Date.now()}-${file.originalname}`);
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
limits: { fileSize: 100 * 1024 * 1024 }, // 100MB
|
||||
fileFilter: (req, file, cb) => {
|
||||
if (file.originalname.match(/\.(py|pyx|pyi)$/)) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('只支持 Python 文件 (.py, .pyx, .pyi)'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 项目存储目录
|
||||
const PROJECTS_DIR = path.join(__dirname, 'projects_data');
|
||||
if (!fs.existsSync(PROJECTS_DIR)) {
|
||||
fs.mkdirSync(PROJECTS_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
// 项目数据存储(简化版,生产环境应使用数据库)
|
||||
let projects = [];
|
||||
const PROJECTS_FILE = path.join(PROJECTS_DIR, 'projects.json');
|
||||
|
||||
// 加载项目数据
|
||||
function loadProjects() {
|
||||
try {
|
||||
if (fs.existsSync(PROJECTS_FILE)) {
|
||||
const data = fs.readFileSync(PROJECTS_FILE, 'utf8');
|
||||
projects = JSON.parse(data);
|
||||
console.log(`加载了 ${projects.length} 个项目`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载项目数据失败:', error);
|
||||
projects = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 保存项目数据
|
||||
function saveProjects() {
|
||||
try {
|
||||
fs.writeFileSync(PROJECTS_FILE, JSON.stringify(projects, null, 2));
|
||||
} catch (error) {
|
||||
console.error('保存项目数据失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
loadProjects();
|
||||
|
||||
// 工具配置
|
||||
const TOOL_CONFIG = {
|
||||
bandit: {
|
||||
command: 'python',
|
||||
args: (filePath) => `-m bandit -r -f json ${filePath}`,
|
||||
parseResult: (stdout) => {
|
||||
try {
|
||||
if (!stdout || stdout.trim() === '') return [];
|
||||
const result = JSON.parse(stdout);
|
||||
return (result.results || []).map(item => ({
|
||||
file: item.filename,
|
||||
line: item.line_number,
|
||||
column: 0,
|
||||
rule: item.test_id,
|
||||
message: item.issue_text,
|
||||
severity: item.issue_severity.toLowerCase(),
|
||||
confidence: item.issue_confidence.toLowerCase(),
|
||||
type: item.issue_severity === 'HIGH' ? 'error' : 'warning'
|
||||
}));
|
||||
} catch (e) {
|
||||
console.error('Bandit解析失败:', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
flake8: {
|
||||
command: 'python',
|
||||
args: (filePath) => `-m flake8 ${filePath}`,
|
||||
parseResult: (stdout) => {
|
||||
try {
|
||||
if (!stdout || stdout.trim() === '') return [];
|
||||
|
||||
const lines = stdout.trim().split('\n').filter(line => line.trim());
|
||||
const issues = [];
|
||||
|
||||
for (const line of lines) {
|
||||
const match = line.match(/^(.+?):(\d+):(\d+):\s*([A-Z]\d+)\s*(.+)$/);
|
||||
if (match) {
|
||||
const code = match[4];
|
||||
issues.push({
|
||||
file: match[1],
|
||||
line: parseInt(match[2]),
|
||||
column: parseInt(match[3]),
|
||||
rule: code,
|
||||
message: match[5],
|
||||
severity: code.startsWith('E') ? 'high' : 'medium',
|
||||
type: code.startsWith('E') ? 'error' : 'warning'
|
||||
});
|
||||
}
|
||||
}
|
||||
return issues;
|
||||
} catch (e) {
|
||||
console.error('Flake8解析失败:', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
pylint: {
|
||||
command: 'python',
|
||||
args: (filePath) => `-m pylint --output-format=json ${filePath}`,
|
||||
parseResult: (stdout) => {
|
||||
try {
|
||||
if (!stdout || stdout.trim() === '') return [];
|
||||
const result = JSON.parse(stdout);
|
||||
return result.map(item => ({
|
||||
file: item.path,
|
||||
line: item.line,
|
||||
column: item.column,
|
||||
rule: item.symbol,
|
||||
message: item.message,
|
||||
severity: item.type === 'error' ? 'high' : item.type === 'warning' ? 'medium' : 'low',
|
||||
type: item.type === 'error' ? 'error' : item.type === 'warning' ? 'warning' : 'info'
|
||||
}));
|
||||
} catch (e) {
|
||||
console.error('Pylint解析失败:', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 运行单个工具检查
|
||||
async function runTool(tool, filePath) {
|
||||
return new Promise((resolve) => {
|
||||
const config = TOOL_CONFIG[tool];
|
||||
if (!config) {
|
||||
return resolve({ status: 'error', issues: [] });
|
||||
}
|
||||
|
||||
const command = `${config.command} ${config.args(filePath)}`;
|
||||
console.log(`执行命令: ${command}`);
|
||||
|
||||
exec(command, { shell: true, timeout: 60000 }, (error, stdout, stderr) => {
|
||||
console.log(`${tool} 执行完成`);
|
||||
|
||||
try {
|
||||
const issues = config.parseResult(stdout || '');
|
||||
resolve({
|
||||
status: 'completed',
|
||||
issues: issues,
|
||||
raw_output: stdout
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(`${tool} 处理结果失败:`, e);
|
||||
resolve({
|
||||
status: 'error',
|
||||
issues: [],
|
||||
error: e.message
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 运行代码检查
|
||||
async function runCodeCheck(filePath) {
|
||||
const tools = ['pylint', 'flake8', 'bandit'];
|
||||
const toolsStatus = {};
|
||||
const allIssues = [];
|
||||
|
||||
for (const tool of tools) {
|
||||
try {
|
||||
const result = await runTool(tool, filePath);
|
||||
toolsStatus[tool] = result.status;
|
||||
allIssues.push(...result.issues);
|
||||
} catch (error) {
|
||||
console.error(`${tool} 执行失败:`, error);
|
||||
toolsStatus[tool] = 'error';
|
||||
}
|
||||
}
|
||||
|
||||
// 统计问题
|
||||
const errorCount = allIssues.filter(i => i.type === 'error').length;
|
||||
const warningCount = allIssues.filter(i => i.type === 'warning').length;
|
||||
const infoCount = allIssues.filter(i => i.type === 'info').length;
|
||||
|
||||
return {
|
||||
tools_status: toolsStatus,
|
||||
all_issues: allIssues,
|
||||
total_issues: allIssues.length,
|
||||
error_count: errorCount,
|
||||
warning_count: warningCount,
|
||||
info_count: infoCount
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== API 端点 ====================
|
||||
|
||||
// 健康检查
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.json({
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
// 文件上传端点
|
||||
app.post('/api/upload', upload.array('files'), (req, res) => {
|
||||
try {
|
||||
if (!req.files || req.files.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: '没有上传文件'
|
||||
});
|
||||
}
|
||||
|
||||
// 创建临时目录
|
||||
const tempDir = path.join(os.tmpdir(), `fortifycode_${Date.now()}`);
|
||||
fs.mkdirSync(tempDir, { recursive: true });
|
||||
|
||||
// 复制文件到临时目录
|
||||
req.files.forEach(file => {
|
||||
const targetPath = path.join(tempDir, file.originalname);
|
||||
fs.copyFileSync(file.path, targetPath);
|
||||
// 删除原始上传文件
|
||||
fs.unlinkSync(file.path);
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
temp_path: tempDir,
|
||||
files: req.files.map(f => f.originalname)
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('文件上传失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 代码检查端点
|
||||
app.post('/api/check', async (req, res) => {
|
||||
try {
|
||||
const { temp_path } = req.body;
|
||||
|
||||
if (!temp_path || !fs.existsSync(temp_path)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: '无效的临时路径'
|
||||
});
|
||||
}
|
||||
|
||||
// 获取所有Python文件
|
||||
const files = fs.readdirSync(temp_path).filter(f => f.endsWith('.py'));
|
||||
|
||||
if (files.length === 0) {
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
tools_status: { pylint: 'completed', flake8: 'completed', bandit: 'completed' },
|
||||
all_issues: [],
|
||||
total_issues: 0,
|
||||
error_count: 0,
|
||||
warning_count: 0,
|
||||
info_count: 0
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 检查第一个文件(简化版,可以扩展为检查所有文件)
|
||||
const firstFile = path.join(temp_path, files[0]);
|
||||
const result = await runCodeCheck(firstFile);
|
||||
|
||||
// 清理临时文件
|
||||
setTimeout(() => {
|
||||
try {
|
||||
fs.rmSync(temp_path, { recursive: true, force: true });
|
||||
} catch (e) {
|
||||
console.error('清理临时文件失败:', e);
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('代码检查失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取所有项目
|
||||
app.get('/api/projects', (req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
data: projects
|
||||
});
|
||||
});
|
||||
|
||||
// 创建新项目
|
||||
app.post('/api/projects', (req, res) => {
|
||||
try {
|
||||
const { name, description, source_type, source_url } = req.body;
|
||||
|
||||
if (!name) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: '项目名称不能为空'
|
||||
});
|
||||
}
|
||||
|
||||
const project = {
|
||||
id: Date.now(),
|
||||
name,
|
||||
description: description || '',
|
||||
source_type: source_type || 'upload',
|
||||
source_url: source_url || '',
|
||||
status: 'pending',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
latest_check: null
|
||||
};
|
||||
|
||||
// 创建项目目录
|
||||
const projectDir = path.join(PROJECTS_DIR, `project_${project.id}`);
|
||||
fs.mkdirSync(projectDir, { recursive: true });
|
||||
project.path = projectDir;
|
||||
|
||||
projects.push(project);
|
||||
saveProjects();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: project
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建项目失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取项目详情
|
||||
app.get('/api/projects/:id', (req, res) => {
|
||||
const projectId = parseInt(req.params.id);
|
||||
const project = projects.find(p => p.id === projectId);
|
||||
|
||||
if (!project) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: '项目不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: project
|
||||
});
|
||||
});
|
||||
|
||||
// 删除项目
|
||||
app.delete('/api/projects/:id', (req, res) => {
|
||||
try {
|
||||
const projectId = parseInt(req.params.id);
|
||||
const projectIndex = projects.findIndex(p => p.id === projectId);
|
||||
|
||||
if (projectIndex === -1) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: '项目不存在'
|
||||
});
|
||||
}
|
||||
|
||||
const project = projects[projectIndex];
|
||||
|
||||
// 删除项目目录
|
||||
if (project.path && fs.existsSync(project.path)) {
|
||||
fs.rmSync(project.path, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
// 从列表中删除
|
||||
projects.splice(projectIndex, 1);
|
||||
saveProjects();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '项目删除成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('删除项目失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 运行项目检查
|
||||
app.post('/api/projects/:id/check', async (req, res) => {
|
||||
try {
|
||||
const projectId = parseInt(req.params.id);
|
||||
const project = projects.find(p => p.id === projectId);
|
||||
|
||||
if (!project) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: '项目不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 获取项目中的所有Python文件
|
||||
const projectPath = project.path;
|
||||
const files = [];
|
||||
|
||||
function getAllPythonFiles(dir) {
|
||||
const items = fs.readdirSync(dir);
|
||||
items.forEach(item => {
|
||||
const fullPath = path.join(dir, item);
|
||||
const stat = fs.statSync(fullPath);
|
||||
if (stat.isDirectory()) {
|
||||
getAllPythonFiles(fullPath);
|
||||
} else if (item.endsWith('.py')) {
|
||||
files.push(fullPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getAllPythonFiles(projectPath);
|
||||
|
||||
if (files.length === 0) {
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
check_id: Date.now(),
|
||||
status: 'completed',
|
||||
total_issues: 0,
|
||||
message: '项目中没有Python文件'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 检查第一个文件作为示例
|
||||
const result = await runCodeCheck(files[0]);
|
||||
|
||||
// 更新项目的最新检查记录
|
||||
project.latest_check = {
|
||||
id: Date.now(),
|
||||
status: 'completed',
|
||||
started_at: new Date().toISOString(),
|
||||
completed_at: new Date().toISOString(),
|
||||
total_issues: result.total_issues,
|
||||
error_count: result.error_count,
|
||||
warning_count: result.warning_count,
|
||||
info_count: result.info_count
|
||||
};
|
||||
project.status = 'completed';
|
||||
project.updated_at = new Date().toISOString();
|
||||
saveProjects();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
check_id: project.latest_check.id,
|
||||
...result
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('项目检查失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 上传文件到项目
|
||||
app.post('/api/projects/:id/upload-files', upload.array('files'), (req, res) => {
|
||||
try {
|
||||
const projectId = parseInt(req.params.id);
|
||||
const project = projects.find(p => p.id === projectId);
|
||||
|
||||
if (!project) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: '项目不存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (!req.files || req.files.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: '没有上传文件'
|
||||
});
|
||||
}
|
||||
|
||||
// 复制文件到项目目录
|
||||
let uploadedCount = 0;
|
||||
req.files.forEach(file => {
|
||||
const targetPath = path.join(project.path, file.originalname);
|
||||
fs.copyFileSync(file.path, targetPath);
|
||||
fs.unlinkSync(file.path);
|
||||
uploadedCount++;
|
||||
});
|
||||
|
||||
project.updated_at = new Date().toISOString();
|
||||
saveProjects();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
uploaded_count: uploadedCount
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('文件上传失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取项目文件列表
|
||||
app.get('/api/projects/:id/files', (req, res) => {
|
||||
try {
|
||||
const projectId = parseInt(req.params.id);
|
||||
const project = projects.find(p => p.id === projectId);
|
||||
|
||||
if (!project) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: '项目不存在'
|
||||
});
|
||||
}
|
||||
|
||||
const targetPath = req.query.path || '';
|
||||
const fullPath = path.join(project.path, targetPath);
|
||||
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
return res.json({
|
||||
success: true,
|
||||
data: []
|
||||
});
|
||||
}
|
||||
|
||||
const items = fs.readdirSync(fullPath);
|
||||
const files = items.map(item => {
|
||||
const itemPath = path.join(fullPath, item);
|
||||
const stat = fs.statSync(itemPath);
|
||||
return {
|
||||
name: item,
|
||||
path: path.join(targetPath, item),
|
||||
is_directory: stat.isDirectory(),
|
||||
size: stat.size,
|
||||
modified_at: stat.mtime.toISOString()
|
||||
};
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: files
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取文件列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取文件内容
|
||||
app.get('/api/projects/:id/files/content', (req, res) => {
|
||||
try {
|
||||
const projectId = parseInt(req.params.id);
|
||||
const project = projects.find(p => p.id === projectId);
|
||||
|
||||
if (!project) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: '项目不存在'
|
||||
});
|
||||
}
|
||||
|
||||
const filePath = req.query.path;
|
||||
if (!filePath) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: '文件路径不能为空'
|
||||
});
|
||||
}
|
||||
|
||||
const fullPath = path.join(project.path, filePath);
|
||||
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: '文件不存在'
|
||||
});
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(fullPath, 'utf8');
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
path: filePath,
|
||||
content: content
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('读取文件失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 保存文件内容
|
||||
app.put('/api/projects/:id/files/content', (req, res) => {
|
||||
try {
|
||||
const projectId = parseInt(req.params.id);
|
||||
const project = projects.find(p => p.id === projectId);
|
||||
|
||||
if (!project) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: '项目不存在'
|
||||
});
|
||||
}
|
||||
|
||||
const { path: filePath, content } = req.body;
|
||||
if (!filePath) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: '文件路径不能为空'
|
||||
});
|
||||
}
|
||||
|
||||
const fullPath = path.join(project.path, filePath);
|
||||
const dirPath = path.dirname(fullPath);
|
||||
|
||||
// 确保目录存在
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
|
||||
fs.writeFileSync(fullPath, content || '');
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '文件保存成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('保存文件失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 静态文件服务 - 前端
|
||||
app.use(express.static(path.join(__dirname, 'frontend')));
|
||||
|
||||
// 默认路由 - 返回前端页面
|
||||
app.get('/', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'frontend', 'index.html'));
|
||||
});
|
||||
|
||||
// 404处理
|
||||
app.use((req, res) => {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: '接口不存在'
|
||||
});
|
||||
});
|
||||
|
||||
// 错误处理中间件
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('服务器错误:', err);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: err.message || '服务器内部错误'
|
||||
});
|
||||
});
|
||||
|
||||
// 启动服务器
|
||||
app.listen(PORT, () => {
|
||||
console.log('=================================');
|
||||
console.log('FortifyCode 后端服务器已启动');
|
||||
console.log(`服务器地址: http://localhost:${PORT}`);
|
||||
console.log(`API 地址: http://localhost:${PORT}/api`);
|
||||
console.log(`前端地址: http://localhost:${PORT}`);
|
||||
console.log(`项目数据目录: ${PROJECTS_DIR}`);
|
||||
console.log('=================================');
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
# FortifyCode Python 依赖
|
||||
|
||||
# 代码质量检查工具
|
||||
pylint>=2.17.0
|
||||
flake8>=6.0.0
|
||||
bandit>=1.7.5
|
||||
|
||||
# 可选:增强功能
|
||||
pycodestyle>=2.10.0
|
||||
mccabe>=0.7.0
|
||||
|
||||
@ -0,0 +1,217 @@
|
||||
# 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 中的"常见问题"部分
|
||||
|
||||
---
|
||||
|
||||
祝使用愉快! 🚀
|
||||
|
||||
Loading…
Reference in new issue