feat(api): 初始化笔记应用后端基础架构
- 添加环境配置文件 .env.example 包含数据库、JWT、CORS等配置 - 创建 .gitignore 文件忽略敏感文件和临时文件 - 配置 Apache 重写规则支持路由转发 - 实现 JWT 认证中间件提供用户身份验证功能 - 添加 MySQL 数据库初始化脚本包含分组、图片、笔记表结构
This commit is contained in:
96
.spec-workflow/templates/design-template.md
Normal file
96
.spec-workflow/templates/design-template.md
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
# Design Document
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
[High-level description of the feature and its place in the overall system]
|
||||||
|
|
||||||
|
## Steering Document Alignment
|
||||||
|
|
||||||
|
### Technical Standards (tech.md)
|
||||||
|
[How the design follows documented technical patterns and standards]
|
||||||
|
|
||||||
|
### Project Structure (structure.md)
|
||||||
|
[How the implementation will follow project organization conventions]
|
||||||
|
|
||||||
|
## Code Reuse Analysis
|
||||||
|
[What existing code will be leveraged, extended, or integrated with this feature]
|
||||||
|
|
||||||
|
### Existing Components to Leverage
|
||||||
|
- **[Component/Utility Name]**: [How it will be used]
|
||||||
|
- **[Service/Helper Name]**: [How it will be extended]
|
||||||
|
|
||||||
|
### Integration Points
|
||||||
|
- **[Existing System/API]**: [How the new feature will integrate]
|
||||||
|
- **[Database/Storage]**: [How data will connect to existing schemas]
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
[Describe the overall architecture and design patterns used]
|
||||||
|
|
||||||
|
### Modular Design Principles
|
||||||
|
- **Single File Responsibility**: Each file should handle one specific concern or domain
|
||||||
|
- **Component Isolation**: Create small, focused components rather than large monolithic files
|
||||||
|
- **Service Layer Separation**: Separate data access, business logic, and presentation layers
|
||||||
|
- **Utility Modularity**: Break utilities into focused, single-purpose modules
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Component A] --> B[Component B]
|
||||||
|
B --> C[Component C]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Components and Interfaces
|
||||||
|
|
||||||
|
### Component 1
|
||||||
|
- **Purpose:** [What this component does]
|
||||||
|
- **Interfaces:** [Public methods/APIs]
|
||||||
|
- **Dependencies:** [What it depends on]
|
||||||
|
- **Reuses:** [Existing components/utilities it builds upon]
|
||||||
|
|
||||||
|
### Component 2
|
||||||
|
- **Purpose:** [What this component does]
|
||||||
|
- **Interfaces:** [Public methods/APIs]
|
||||||
|
- **Dependencies:** [What it depends on]
|
||||||
|
- **Reuses:** [Existing components/utilities it builds upon]
|
||||||
|
|
||||||
|
## Data Models
|
||||||
|
|
||||||
|
### Model 1
|
||||||
|
```
|
||||||
|
[Define the structure of Model1 in your language]
|
||||||
|
- id: [unique identifier type]
|
||||||
|
- name: [string/text type]
|
||||||
|
- [Additional properties as needed]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Model 2
|
||||||
|
```
|
||||||
|
[Define the structure of Model2 in your language]
|
||||||
|
- id: [unique identifier type]
|
||||||
|
- [Additional properties as needed]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
### Error Scenarios
|
||||||
|
1. **Scenario 1:** [Description]
|
||||||
|
- **Handling:** [How to handle]
|
||||||
|
- **User Impact:** [What user sees]
|
||||||
|
|
||||||
|
2. **Scenario 2:** [Description]
|
||||||
|
- **Handling:** [How to handle]
|
||||||
|
- **User Impact:** [What user sees]
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
### Unit Testing
|
||||||
|
- [Unit testing approach]
|
||||||
|
- [Key components to test]
|
||||||
|
|
||||||
|
### Integration Testing
|
||||||
|
- [Integration testing approach]
|
||||||
|
- [Key flows to test]
|
||||||
|
|
||||||
|
### End-to-End Testing
|
||||||
|
- [E2E testing approach]
|
||||||
|
- [User scenarios to test]
|
||||||
51
.spec-workflow/templates/product-template.md
Normal file
51
.spec-workflow/templates/product-template.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Product Overview
|
||||||
|
|
||||||
|
## Product Purpose
|
||||||
|
[Describe the core purpose of this product/project. What problem does it solve?]
|
||||||
|
|
||||||
|
## Target Users
|
||||||
|
[Who are the primary users of this product? What are their needs and pain points?]
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
[List the main features that deliver value to users]
|
||||||
|
|
||||||
|
1. **Feature 1**: [Description]
|
||||||
|
2. **Feature 2**: [Description]
|
||||||
|
3. **Feature 3**: [Description]
|
||||||
|
|
||||||
|
## Business Objectives
|
||||||
|
[What are the business goals this product aims to achieve?]
|
||||||
|
|
||||||
|
- [Objective 1]
|
||||||
|
- [Objective 2]
|
||||||
|
- [Objective 3]
|
||||||
|
|
||||||
|
## Success Metrics
|
||||||
|
[How will we measure the success of this product?]
|
||||||
|
|
||||||
|
- [Metric 1]: [Target]
|
||||||
|
- [Metric 2]: [Target]
|
||||||
|
- [Metric 3]: [Target]
|
||||||
|
|
||||||
|
## Product Principles
|
||||||
|
[Core principles that guide product decisions]
|
||||||
|
|
||||||
|
1. **[Principle 1]**: [Explanation]
|
||||||
|
2. **[Principle 2]**: [Explanation]
|
||||||
|
3. **[Principle 3]**: [Explanation]
|
||||||
|
|
||||||
|
## Monitoring & Visibility (if applicable)
|
||||||
|
[How do users track progress and monitor the system?]
|
||||||
|
|
||||||
|
- **Dashboard Type**: [e.g., Web-based, CLI, Desktop app]
|
||||||
|
- **Real-time Updates**: [e.g., WebSocket, polling, push notifications]
|
||||||
|
- **Key Metrics Displayed**: [What information is most important to surface]
|
||||||
|
- **Sharing Capabilities**: [e.g., read-only links, exports, reports]
|
||||||
|
|
||||||
|
## Future Vision
|
||||||
|
[Where do we see this product evolving in the future?]
|
||||||
|
|
||||||
|
### Potential Enhancements
|
||||||
|
- **Remote Access**: [e.g., Tunnel features for sharing dashboards with stakeholders]
|
||||||
|
- **Analytics**: [e.g., Historical trends, performance metrics]
|
||||||
|
- **Collaboration**: [e.g., Multi-user support, commenting]
|
||||||
50
.spec-workflow/templates/requirements-template.md
Normal file
50
.spec-workflow/templates/requirements-template.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Requirements Document
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
[Provide a brief overview of the feature, its purpose, and its value to users]
|
||||||
|
|
||||||
|
## Alignment with Product Vision
|
||||||
|
|
||||||
|
[Explain how this feature supports the goals outlined in product.md]
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### Requirement 1
|
||||||
|
|
||||||
|
**User Story:** As a [role], I want [feature], so that [benefit]
|
||||||
|
|
||||||
|
#### Acceptance Criteria
|
||||||
|
|
||||||
|
1. WHEN [event] THEN [system] SHALL [response]
|
||||||
|
2. IF [precondition] THEN [system] SHALL [response]
|
||||||
|
3. WHEN [event] AND [condition] THEN [system] SHALL [response]
|
||||||
|
|
||||||
|
### Requirement 2
|
||||||
|
|
||||||
|
**User Story:** As a [role], I want [feature], so that [benefit]
|
||||||
|
|
||||||
|
#### Acceptance Criteria
|
||||||
|
|
||||||
|
1. WHEN [event] THEN [system] SHALL [response]
|
||||||
|
2. IF [precondition] THEN [system] SHALL [response]
|
||||||
|
|
||||||
|
## Non-Functional Requirements
|
||||||
|
|
||||||
|
### Code Architecture and Modularity
|
||||||
|
- **Single Responsibility Principle**: Each file should have a single, well-defined purpose
|
||||||
|
- **Modular Design**: Components, utilities, and services should be isolated and reusable
|
||||||
|
- **Dependency Management**: Minimize interdependencies between modules
|
||||||
|
- **Clear Interfaces**: Define clean contracts between components and layers
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
- [Performance requirements]
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- [Security requirements]
|
||||||
|
|
||||||
|
### Reliability
|
||||||
|
- [Reliability requirements]
|
||||||
|
|
||||||
|
### Usability
|
||||||
|
- [Usability requirements]
|
||||||
145
.spec-workflow/templates/structure-template.md
Normal file
145
.spec-workflow/templates/structure-template.md
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
# Project Structure
|
||||||
|
|
||||||
|
## Directory Organization
|
||||||
|
|
||||||
|
```
|
||||||
|
[Define your project's directory structure. Examples below - adapt to your project type]
|
||||||
|
|
||||||
|
Example for a library/package:
|
||||||
|
project-root/
|
||||||
|
├── src/ # Source code
|
||||||
|
├── tests/ # Test files
|
||||||
|
├── docs/ # Documentation
|
||||||
|
├── examples/ # Usage examples
|
||||||
|
└── [build/dist/out] # Build output
|
||||||
|
|
||||||
|
Example for an application:
|
||||||
|
project-root/
|
||||||
|
├── [src/app/lib] # Main source code
|
||||||
|
├── [assets/resources] # Static resources
|
||||||
|
├── [config/settings] # Configuration
|
||||||
|
├── [scripts/tools] # Build/utility scripts
|
||||||
|
└── [tests/spec] # Test files
|
||||||
|
|
||||||
|
Common patterns:
|
||||||
|
- Group by feature/module
|
||||||
|
- Group by layer (UI, business logic, data)
|
||||||
|
- Group by type (models, controllers, views)
|
||||||
|
- Flat structure for simple projects
|
||||||
|
```
|
||||||
|
|
||||||
|
## Naming Conventions
|
||||||
|
|
||||||
|
### Files
|
||||||
|
- **Components/Modules**: [e.g., `PascalCase`, `snake_case`, `kebab-case`]
|
||||||
|
- **Services/Handlers**: [e.g., `UserService`, `user_service`, `user-service`]
|
||||||
|
- **Utilities/Helpers**: [e.g., `dateUtils`, `date_utils`, `date-utils`]
|
||||||
|
- **Tests**: [e.g., `[filename]_test`, `[filename].test`, `[filename]Test`]
|
||||||
|
|
||||||
|
### Code
|
||||||
|
- **Classes/Types**: [e.g., `PascalCase`, `CamelCase`, `snake_case`]
|
||||||
|
- **Functions/Methods**: [e.g., `camelCase`, `snake_case`, `PascalCase`]
|
||||||
|
- **Constants**: [e.g., `UPPER_SNAKE_CASE`, `SCREAMING_CASE`, `PascalCase`]
|
||||||
|
- **Variables**: [e.g., `camelCase`, `snake_case`, `lowercase`]
|
||||||
|
|
||||||
|
## Import Patterns
|
||||||
|
|
||||||
|
### Import Order
|
||||||
|
1. External dependencies
|
||||||
|
2. Internal modules
|
||||||
|
3. Relative imports
|
||||||
|
4. Style imports
|
||||||
|
|
||||||
|
### Module/Package Organization
|
||||||
|
```
|
||||||
|
[Describe your project's import/include patterns]
|
||||||
|
Examples:
|
||||||
|
- Absolute imports from project root
|
||||||
|
- Relative imports within modules
|
||||||
|
- Package/namespace organization
|
||||||
|
- Dependency management approach
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Structure Patterns
|
||||||
|
|
||||||
|
[Define common patterns for organizing code within files. Below are examples - choose what applies to your project]
|
||||||
|
|
||||||
|
### Module/Class Organization
|
||||||
|
```
|
||||||
|
Example patterns:
|
||||||
|
1. Imports/includes/dependencies
|
||||||
|
2. Constants and configuration
|
||||||
|
3. Type/interface definitions
|
||||||
|
4. Main implementation
|
||||||
|
5. Helper/utility functions
|
||||||
|
6. Exports/public API
|
||||||
|
```
|
||||||
|
|
||||||
|
### Function/Method Organization
|
||||||
|
```
|
||||||
|
Example patterns:
|
||||||
|
- Input validation first
|
||||||
|
- Core logic in the middle
|
||||||
|
- Error handling throughout
|
||||||
|
- Clear return points
|
||||||
|
```
|
||||||
|
|
||||||
|
### File Organization Principles
|
||||||
|
```
|
||||||
|
Choose what works for your project:
|
||||||
|
- One class/module per file
|
||||||
|
- Related functionality grouped together
|
||||||
|
- Public API at the top/bottom
|
||||||
|
- Implementation details hidden
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Organization Principles
|
||||||
|
|
||||||
|
1. **Single Responsibility**: Each file should have one clear purpose
|
||||||
|
2. **Modularity**: Code should be organized into reusable modules
|
||||||
|
3. **Testability**: Structure code to be easily testable
|
||||||
|
4. **Consistency**: Follow patterns established in the codebase
|
||||||
|
|
||||||
|
## Module Boundaries
|
||||||
|
[Define how different parts of your project interact and maintain separation of concerns]
|
||||||
|
|
||||||
|
Examples of boundary patterns:
|
||||||
|
- **Core vs Plugins**: Core functionality vs extensible plugins
|
||||||
|
- **Public API vs Internal**: What's exposed vs implementation details
|
||||||
|
- **Platform-specific vs Cross-platform**: OS-specific code isolation
|
||||||
|
- **Stable vs Experimental**: Production code vs experimental features
|
||||||
|
- **Dependencies direction**: Which modules can depend on which
|
||||||
|
|
||||||
|
## Code Size Guidelines
|
||||||
|
[Define your project's guidelines for file and function sizes]
|
||||||
|
|
||||||
|
Suggested guidelines:
|
||||||
|
- **File size**: [Define maximum lines per file]
|
||||||
|
- **Function/Method size**: [Define maximum lines per function]
|
||||||
|
- **Class/Module complexity**: [Define complexity limits]
|
||||||
|
- **Nesting depth**: [Maximum nesting levels]
|
||||||
|
|
||||||
|
## Dashboard/Monitoring Structure (if applicable)
|
||||||
|
[How dashboard or monitoring components are organized]
|
||||||
|
|
||||||
|
### Example Structure:
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
└── dashboard/ # Self-contained dashboard subsystem
|
||||||
|
├── server/ # Backend server components
|
||||||
|
├── client/ # Frontend assets
|
||||||
|
├── shared/ # Shared types/utilities
|
||||||
|
└── public/ # Static assets
|
||||||
|
```
|
||||||
|
|
||||||
|
### Separation of Concerns
|
||||||
|
- Dashboard isolated from core business logic
|
||||||
|
- Own CLI entry point for independent operation
|
||||||
|
- Minimal dependencies on main application
|
||||||
|
- Can be disabled without affecting core functionality
|
||||||
|
|
||||||
|
## Documentation Standards
|
||||||
|
- All public APIs must have documentation
|
||||||
|
- Complex logic should include inline comments
|
||||||
|
- README files for major modules
|
||||||
|
- Follow language-specific documentation conventions
|
||||||
139
.spec-workflow/templates/tasks-template.md
Normal file
139
.spec-workflow/templates/tasks-template.md
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
# Tasks Document
|
||||||
|
|
||||||
|
- [ ] 1. Create core interfaces in src/types/feature.ts
|
||||||
|
- File: src/types/feature.ts
|
||||||
|
- Define TypeScript interfaces for feature data structures
|
||||||
|
- Extend existing base interfaces from base.ts
|
||||||
|
- Purpose: Establish type safety for feature implementation
|
||||||
|
- _Leverage: src/types/base.ts_
|
||||||
|
- _Requirements: 1.1_
|
||||||
|
- _Prompt: Role: TypeScript Developer specializing in type systems and interfaces | Task: Create comprehensive TypeScript interfaces for the feature data structures following requirements 1.1, extending existing base interfaces from src/types/base.ts | Restrictions: Do not modify existing base interfaces, maintain backward compatibility, follow project naming conventions | Success: All interfaces compile without errors, proper inheritance from base types, full type coverage for feature requirements_
|
||||||
|
|
||||||
|
- [ ] 2. Create base model class in src/models/FeatureModel.ts
|
||||||
|
- File: src/models/FeatureModel.ts
|
||||||
|
- Implement base model extending BaseModel class
|
||||||
|
- Add validation methods using existing validation utilities
|
||||||
|
- Purpose: Provide data layer foundation for feature
|
||||||
|
- _Leverage: src/models/BaseModel.ts, src/utils/validation.ts_
|
||||||
|
- _Requirements: 2.1_
|
||||||
|
- _Prompt: Role: Backend Developer with expertise in Node.js and data modeling | Task: Create a base model class extending BaseModel and implementing validation following requirement 2.1, leveraging existing patterns from src/models/BaseModel.ts and src/utils/validation.ts | Restrictions: Must follow existing model patterns, do not bypass validation utilities, maintain consistent error handling | Success: Model extends BaseModel correctly, validation methods implemented and tested, follows project architecture patterns_
|
||||||
|
|
||||||
|
- [ ] 3. Add specific model methods to FeatureModel.ts
|
||||||
|
- File: src/models/FeatureModel.ts (continue from task 2)
|
||||||
|
- Implement create, update, delete methods
|
||||||
|
- Add relationship handling for foreign keys
|
||||||
|
- Purpose: Complete model functionality for CRUD operations
|
||||||
|
- _Leverage: src/models/BaseModel.ts_
|
||||||
|
- _Requirements: 2.2, 2.3_
|
||||||
|
- _Prompt: Role: Backend Developer with expertise in ORM and database operations | Task: Implement CRUD methods and relationship handling in FeatureModel.ts following requirements 2.2 and 2.3, extending patterns from src/models/BaseModel.ts | Restrictions: Must maintain transaction integrity, follow existing relationship patterns, do not duplicate base model functionality | Success: All CRUD operations work correctly, relationships are properly handled, database operations are atomic and efficient_
|
||||||
|
|
||||||
|
- [ ] 4. Create model unit tests in tests/models/FeatureModel.test.ts
|
||||||
|
- File: tests/models/FeatureModel.test.ts
|
||||||
|
- Write tests for model validation and CRUD methods
|
||||||
|
- Use existing test utilities and fixtures
|
||||||
|
- Purpose: Ensure model reliability and catch regressions
|
||||||
|
- _Leverage: tests/helpers/testUtils.ts, tests/fixtures/data.ts_
|
||||||
|
- _Requirements: 2.1, 2.2_
|
||||||
|
- _Prompt: Role: QA Engineer with expertise in unit testing and Jest/Mocha frameworks | Task: Create comprehensive unit tests for FeatureModel validation and CRUD methods covering requirements 2.1 and 2.2, using existing test utilities from tests/helpers/testUtils.ts and fixtures from tests/fixtures/data.ts | Restrictions: Must test both success and failure scenarios, do not test external dependencies directly, maintain test isolation | Success: All model methods are tested with good coverage, edge cases covered, tests run independently and consistently_
|
||||||
|
|
||||||
|
- [ ] 5. Create service interface in src/services/IFeatureService.ts
|
||||||
|
- File: src/services/IFeatureService.ts
|
||||||
|
- Define service contract with method signatures
|
||||||
|
- Extend base service interface patterns
|
||||||
|
- Purpose: Establish service layer contract for dependency injection
|
||||||
|
- _Leverage: src/services/IBaseService.ts_
|
||||||
|
- _Requirements: 3.1_
|
||||||
|
- _Prompt: Role: Software Architect specializing in service-oriented architecture and TypeScript interfaces | Task: Design service interface contract following requirement 3.1, extending base service patterns from src/services/IBaseService.ts for dependency injection | Restrictions: Must maintain interface segregation principle, do not expose internal implementation details, ensure contract compatibility with DI container | Success: Interface is well-defined with clear method signatures, extends base service appropriately, supports all required service operations_
|
||||||
|
|
||||||
|
- [ ] 6. Implement feature service in src/services/FeatureService.ts
|
||||||
|
- File: src/services/FeatureService.ts
|
||||||
|
- Create concrete service implementation using FeatureModel
|
||||||
|
- Add error handling with existing error utilities
|
||||||
|
- Purpose: Provide business logic layer for feature operations
|
||||||
|
- _Leverage: src/services/BaseService.ts, src/utils/errorHandler.ts, src/models/FeatureModel.ts_
|
||||||
|
- _Requirements: 3.2_
|
||||||
|
- _Prompt: Role: Backend Developer with expertise in service layer architecture and business logic | Task: Implement concrete FeatureService following requirement 3.2, using FeatureModel and extending BaseService patterns with proper error handling from src/utils/errorHandler.ts | Restrictions: Must implement interface contract exactly, do not bypass model validation, maintain separation of concerns from data layer | Success: Service implements all interface methods correctly, robust error handling implemented, business logic is well-encapsulated and testable_
|
||||||
|
|
||||||
|
- [ ] 7. Add service dependency injection in src/utils/di.ts
|
||||||
|
- File: src/utils/di.ts (modify existing)
|
||||||
|
- Register FeatureService in dependency injection container
|
||||||
|
- Configure service lifetime and dependencies
|
||||||
|
- Purpose: Enable service injection throughout application
|
||||||
|
- _Leverage: existing DI configuration in src/utils/di.ts_
|
||||||
|
- _Requirements: 3.1_
|
||||||
|
- _Prompt: Role: DevOps Engineer with expertise in dependency injection and IoC containers | Task: Register FeatureService in DI container following requirement 3.1, configuring appropriate lifetime and dependencies using existing patterns from src/utils/di.ts | Restrictions: Must follow existing DI container patterns, do not create circular dependencies, maintain service resolution efficiency | Success: FeatureService is properly registered and resolvable, dependencies are correctly configured, service lifetime is appropriate for use case_
|
||||||
|
|
||||||
|
- [ ] 8. Create service unit tests in tests/services/FeatureService.test.ts
|
||||||
|
- File: tests/services/FeatureService.test.ts
|
||||||
|
- Write tests for service methods with mocked dependencies
|
||||||
|
- Test error handling scenarios
|
||||||
|
- Purpose: Ensure service reliability and proper error handling
|
||||||
|
- _Leverage: tests/helpers/testUtils.ts, tests/mocks/modelMocks.ts_
|
||||||
|
- _Requirements: 3.2, 3.3_
|
||||||
|
- _Prompt: Role: QA Engineer with expertise in service testing and mocking frameworks | Task: Create comprehensive unit tests for FeatureService methods covering requirements 3.2 and 3.3, using mocked dependencies from tests/mocks/modelMocks.ts and test utilities | Restrictions: Must mock all external dependencies, test business logic in isolation, do not test framework code | Success: All service methods tested with proper mocking, error scenarios covered, tests verify business logic correctness and error handling_
|
||||||
|
|
||||||
|
- [ ] 4. Create API endpoints
|
||||||
|
- Design API structure
|
||||||
|
- _Leverage: src/api/baseApi.ts, src/utils/apiUtils.ts_
|
||||||
|
- _Requirements: 4.0_
|
||||||
|
- _Prompt: Role: API Architect specializing in RESTful design and Express.js | Task: Design comprehensive API structure following requirement 4.0, leveraging existing patterns from src/api/baseApi.ts and utilities from src/utils/apiUtils.ts | Restrictions: Must follow REST conventions, maintain API versioning compatibility, do not expose internal data structures directly | Success: API structure is well-designed and documented, follows existing patterns, supports all required operations with proper HTTP methods and status codes_
|
||||||
|
|
||||||
|
- [ ] 4.1 Set up routing and middleware
|
||||||
|
- Configure application routes
|
||||||
|
- Add authentication middleware
|
||||||
|
- Set up error handling middleware
|
||||||
|
- _Leverage: src/middleware/auth.ts, src/middleware/errorHandler.ts_
|
||||||
|
- _Requirements: 4.1_
|
||||||
|
- _Prompt: Role: Backend Developer with expertise in Express.js middleware and routing | Task: Configure application routes and middleware following requirement 4.1, integrating authentication from src/middleware/auth.ts and error handling from src/middleware/errorHandler.ts | Restrictions: Must maintain middleware order, do not bypass security middleware, ensure proper error propagation | Success: Routes are properly configured with correct middleware chain, authentication works correctly, errors are handled gracefully throughout the request lifecycle_
|
||||||
|
|
||||||
|
- [ ] 4.2 Implement CRUD endpoints
|
||||||
|
- Create API endpoints
|
||||||
|
- Add request validation
|
||||||
|
- Write API integration tests
|
||||||
|
- _Leverage: src/controllers/BaseController.ts, src/utils/validation.ts_
|
||||||
|
- _Requirements: 4.2, 4.3_
|
||||||
|
- _Prompt: Role: Full-stack Developer with expertise in API development and validation | Task: Implement CRUD endpoints following requirements 4.2 and 4.3, extending BaseController patterns and using validation utilities from src/utils/validation.ts | Restrictions: Must validate all inputs, follow existing controller patterns, ensure proper HTTP status codes and responses | Success: All CRUD operations work correctly, request validation prevents invalid data, integration tests pass and cover all endpoints_
|
||||||
|
|
||||||
|
- [ ] 5. Add frontend components
|
||||||
|
- Plan component architecture
|
||||||
|
- _Leverage: src/components/BaseComponent.tsx, src/styles/theme.ts_
|
||||||
|
- _Requirements: 5.0_
|
||||||
|
- _Prompt: Role: Frontend Architect with expertise in React component design and architecture | Task: Plan comprehensive component architecture following requirement 5.0, leveraging base patterns from src/components/BaseComponent.tsx and theme system from src/styles/theme.ts | Restrictions: Must follow existing component patterns, maintain design system consistency, ensure component reusability | Success: Architecture is well-planned and documented, components are properly organized, follows existing patterns and theme system_
|
||||||
|
|
||||||
|
- [ ] 5.1 Create base UI components
|
||||||
|
- Set up component structure
|
||||||
|
- Implement reusable components
|
||||||
|
- Add styling and theming
|
||||||
|
- _Leverage: src/components/BaseComponent.tsx, src/styles/theme.ts_
|
||||||
|
- _Requirements: 5.1_
|
||||||
|
- _Prompt: Role: Frontend Developer specializing in React and component architecture | Task: Create reusable UI components following requirement 5.1, extending BaseComponent patterns and using existing theme system from src/styles/theme.ts | Restrictions: Must use existing theme variables, follow component composition patterns, ensure accessibility compliance | Success: Components are reusable and properly themed, follow existing architecture, accessible and responsive_
|
||||||
|
|
||||||
|
- [ ] 5.2 Implement feature-specific components
|
||||||
|
- Create feature components
|
||||||
|
- Add state management
|
||||||
|
- Connect to API endpoints
|
||||||
|
- _Leverage: src/hooks/useApi.ts, src/components/BaseComponent.tsx_
|
||||||
|
- _Requirements: 5.2, 5.3_
|
||||||
|
- _Prompt: Role: React Developer with expertise in state management and API integration | Task: Implement feature-specific components following requirements 5.2 and 5.3, using API hooks from src/hooks/useApi.ts and extending BaseComponent patterns | Restrictions: Must use existing state management patterns, handle loading and error states properly, maintain component performance | Success: Components are fully functional with proper state management, API integration works smoothly, user experience is responsive and intuitive_
|
||||||
|
|
||||||
|
- [ ] 6. Integration and testing
|
||||||
|
- Plan integration approach
|
||||||
|
- _Leverage: src/utils/integrationUtils.ts, tests/helpers/testUtils.ts_
|
||||||
|
- _Requirements: 6.0_
|
||||||
|
- _Prompt: Role: Integration Engineer with expertise in system integration and testing strategies | Task: Plan comprehensive integration approach following requirement 6.0, leveraging integration utilities from src/utils/integrationUtils.ts and test helpers | Restrictions: Must consider all system components, ensure proper test coverage, maintain integration test reliability | Success: Integration plan is comprehensive and feasible, all system components work together correctly, integration points are well-tested_
|
||||||
|
|
||||||
|
- [ ] 6.1 Write end-to-end tests
|
||||||
|
- Set up E2E testing framework
|
||||||
|
- Write user journey tests
|
||||||
|
- Add test automation
|
||||||
|
- _Leverage: tests/helpers/testUtils.ts, tests/fixtures/data.ts_
|
||||||
|
- _Requirements: All_
|
||||||
|
- _Prompt: Role: QA Automation Engineer with expertise in E2E testing and test frameworks like Cypress or Playwright | Task: Implement comprehensive end-to-end tests covering all requirements, setting up testing framework and user journey tests using test utilities and fixtures | Restrictions: Must test real user workflows, ensure tests are maintainable and reliable, do not test implementation details | Success: E2E tests cover all critical user journeys, tests run reliably in CI/CD pipeline, user experience is validated from end-to-end_
|
||||||
|
|
||||||
|
- [ ] 6.2 Final integration and cleanup
|
||||||
|
- Integrate all components
|
||||||
|
- Fix any integration issues
|
||||||
|
- Clean up code and documentation
|
||||||
|
- _Leverage: src/utils/cleanup.ts, docs/templates/_
|
||||||
|
- _Requirements: All_
|
||||||
|
- _Prompt: Role: Senior Developer with expertise in code quality and system integration | Task: Complete final integration of all components and perform comprehensive cleanup covering all requirements, using cleanup utilities and documentation templates | Restrictions: Must not break existing functionality, ensure code quality standards are met, maintain documentation consistency | Success: All components are fully integrated and working together, code is clean and well-documented, system meets all requirements and quality standards_
|
||||||
99
.spec-workflow/templates/tech-template.md
Normal file
99
.spec-workflow/templates/tech-template.md
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# Technology Stack
|
||||||
|
|
||||||
|
## Project Type
|
||||||
|
[Describe what kind of project this is: web application, CLI tool, desktop application, mobile app, library, API service, embedded system, game, etc.]
|
||||||
|
|
||||||
|
## Core Technologies
|
||||||
|
|
||||||
|
### Primary Language(s)
|
||||||
|
- **Language**: [e.g., Python 3.11, Go 1.21, TypeScript, Rust, C++]
|
||||||
|
- **Runtime/Compiler**: [if applicable]
|
||||||
|
- **Language-specific tools**: [package managers, build tools, etc.]
|
||||||
|
|
||||||
|
### Key Dependencies/Libraries
|
||||||
|
[List the main libraries and frameworks your project depends on]
|
||||||
|
- **[Library/Framework name]**: [Purpose and version]
|
||||||
|
- **[Library/Framework name]**: [Purpose and version]
|
||||||
|
|
||||||
|
### Application Architecture
|
||||||
|
[Describe how your application is structured - this could be MVC, event-driven, plugin-based, client-server, standalone, microservices, monolithic, etc.]
|
||||||
|
|
||||||
|
### Data Storage (if applicable)
|
||||||
|
- **Primary storage**: [e.g., PostgreSQL, files, in-memory, cloud storage]
|
||||||
|
- **Caching**: [e.g., Redis, in-memory, disk cache]
|
||||||
|
- **Data formats**: [e.g., JSON, Protocol Buffers, XML, binary]
|
||||||
|
|
||||||
|
### External Integrations (if applicable)
|
||||||
|
- **APIs**: [External services you integrate with]
|
||||||
|
- **Protocols**: [e.g., HTTP/REST, gRPC, WebSocket, TCP/IP]
|
||||||
|
- **Authentication**: [e.g., OAuth, API keys, certificates]
|
||||||
|
|
||||||
|
### Monitoring & Dashboard Technologies (if applicable)
|
||||||
|
- **Dashboard Framework**: [e.g., React, Vue, vanilla JS, terminal UI]
|
||||||
|
- **Real-time Communication**: [e.g., WebSocket, Server-Sent Events, polling]
|
||||||
|
- **Visualization Libraries**: [e.g., Chart.js, D3, terminal graphs]
|
||||||
|
- **State Management**: [e.g., Redux, Vuex, file system as source of truth]
|
||||||
|
|
||||||
|
## Development Environment
|
||||||
|
|
||||||
|
### Build & Development Tools
|
||||||
|
- **Build System**: [e.g., Make, CMake, Gradle, npm scripts, cargo]
|
||||||
|
- **Package Management**: [e.g., pip, npm, cargo, go mod, apt, brew]
|
||||||
|
- **Development workflow**: [e.g., hot reload, watch mode, REPL]
|
||||||
|
|
||||||
|
### Code Quality Tools
|
||||||
|
- **Static Analysis**: [Tools for code quality and correctness]
|
||||||
|
- **Formatting**: [Code style enforcement tools]
|
||||||
|
- **Testing Framework**: [Unit, integration, and/or end-to-end testing tools]
|
||||||
|
- **Documentation**: [Documentation generation tools]
|
||||||
|
|
||||||
|
### Version Control & Collaboration
|
||||||
|
- **VCS**: [e.g., Git, Mercurial, SVN]
|
||||||
|
- **Branching Strategy**: [e.g., Git Flow, GitHub Flow, trunk-based]
|
||||||
|
- **Code Review Process**: [How code reviews are conducted]
|
||||||
|
|
||||||
|
### Dashboard Development (if applicable)
|
||||||
|
- **Live Reload**: [e.g., Hot module replacement, file watchers]
|
||||||
|
- **Port Management**: [e.g., Dynamic allocation, configurable ports]
|
||||||
|
- **Multi-Instance Support**: [e.g., Running multiple dashboards simultaneously]
|
||||||
|
|
||||||
|
## Deployment & Distribution (if applicable)
|
||||||
|
- **Target Platform(s)**: [Where/how the project runs: cloud, on-premise, desktop, mobile, embedded]
|
||||||
|
- **Distribution Method**: [How users get your software: download, package manager, app store, SaaS]
|
||||||
|
- **Installation Requirements**: [Prerequisites, system requirements]
|
||||||
|
- **Update Mechanism**: [How updates are delivered]
|
||||||
|
|
||||||
|
## Technical Requirements & Constraints
|
||||||
|
|
||||||
|
### Performance Requirements
|
||||||
|
- [e.g., response time, throughput, memory usage, startup time]
|
||||||
|
- [Specific benchmarks or targets]
|
||||||
|
|
||||||
|
### Compatibility Requirements
|
||||||
|
- **Platform Support**: [Operating systems, architectures, versions]
|
||||||
|
- **Dependency Versions**: [Minimum/maximum versions of dependencies]
|
||||||
|
- **Standards Compliance**: [Industry standards, protocols, specifications]
|
||||||
|
|
||||||
|
### Security & Compliance
|
||||||
|
- **Security Requirements**: [Authentication, encryption, data protection]
|
||||||
|
- **Compliance Standards**: [GDPR, HIPAA, SOC2, etc. if applicable]
|
||||||
|
- **Threat Model**: [Key security considerations]
|
||||||
|
|
||||||
|
### Scalability & Reliability
|
||||||
|
- **Expected Load**: [Users, requests, data volume]
|
||||||
|
- **Availability Requirements**: [Uptime targets, disaster recovery]
|
||||||
|
- **Growth Projections**: [How the system needs to scale]
|
||||||
|
|
||||||
|
## Technical Decisions & Rationale
|
||||||
|
[Document key architectural and technology choices]
|
||||||
|
|
||||||
|
### Decision Log
|
||||||
|
1. **[Technology/Pattern Choice]**: [Why this was chosen, alternatives considered]
|
||||||
|
2. **[Architecture Decision]**: [Rationale, trade-offs accepted]
|
||||||
|
3. **[Tool/Library Selection]**: [Reasoning, evaluation criteria]
|
||||||
|
|
||||||
|
## Known Limitations
|
||||||
|
[Document any technical debt, limitations, or areas for improvement]
|
||||||
|
|
||||||
|
- [Limitation 1]: [Impact and potential future solutions]
|
||||||
|
- [Limitation 2]: [Why it exists and when it might be addressed]
|
||||||
64
.spec-workflow/user-templates/README.md
Normal file
64
.spec-workflow/user-templates/README.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# User Templates
|
||||||
|
|
||||||
|
This directory allows you to create custom templates that override the default Spec Workflow templates.
|
||||||
|
|
||||||
|
## How to Use Custom Templates
|
||||||
|
|
||||||
|
1. **Create your custom template file** in this directory with the exact same name as the default template you want to override:
|
||||||
|
- `requirements-template.md` - Override requirements document template
|
||||||
|
- `design-template.md` - Override design document template
|
||||||
|
- `tasks-template.md` - Override tasks document template
|
||||||
|
- `product-template.md` - Override product steering template
|
||||||
|
- `tech-template.md` - Override tech steering template
|
||||||
|
- `structure-template.md` - Override structure steering template
|
||||||
|
|
||||||
|
2. **Template Loading Priority**:
|
||||||
|
- The system first checks this `user-templates/` directory
|
||||||
|
- If a matching template is found here, it will be used
|
||||||
|
- Otherwise, the default template from `templates/` will be used
|
||||||
|
|
||||||
|
## Example Custom Template
|
||||||
|
|
||||||
|
To create a custom requirements template:
|
||||||
|
|
||||||
|
1. Create a file named `requirements-template.md` in this directory
|
||||||
|
2. Add your custom structure, for example:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Requirements Document
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
[Your custom section]
|
||||||
|
|
||||||
|
## Business Requirements
|
||||||
|
[Your custom structure]
|
||||||
|
|
||||||
|
## Technical Requirements
|
||||||
|
[Your custom fields]
|
||||||
|
|
||||||
|
## Custom Sections
|
||||||
|
[Add any sections specific to your workflow]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Template Variables
|
||||||
|
|
||||||
|
Templates can include placeholders that will be replaced when documents are created:
|
||||||
|
- `{{projectName}}` - The name of your project
|
||||||
|
- `{{featureName}}` - The name of the feature being specified
|
||||||
|
- `{{date}}` - The current date
|
||||||
|
- `{{author}}` - The document author
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Start from defaults**: Copy a default template from `../templates/` as a starting point
|
||||||
|
2. **Keep structure consistent**: Maintain similar section headers for tool compatibility
|
||||||
|
3. **Document changes**: Add comments explaining why sections were added/modified
|
||||||
|
4. **Version control**: Track your custom templates in version control
|
||||||
|
5. **Test thoroughly**: Ensure custom templates work with the spec workflow tools
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Custom templates are project-specific and not included in the package distribution
|
||||||
|
- The `templates/` directory contains the default templates which are updated with each version
|
||||||
|
- Your custom templates in this directory are preserved during updates
|
||||||
|
- If a custom template has errors, the system will fall back to the default template
|
||||||
18
biji-php/.env.example
Normal file
18
biji-php/.env.example
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 数据库配置
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_NAME=biji_db
|
||||||
|
DB_USER=root
|
||||||
|
DB_PASS=
|
||||||
|
|
||||||
|
# JWT 配置
|
||||||
|
JWT_SECRET=your-secret-key-change-this
|
||||||
|
JWT_EXPIRE=86400
|
||||||
|
|
||||||
|
# 应用配置
|
||||||
|
APP_ENV=development
|
||||||
|
APP_DEBUG=true
|
||||||
|
UPLOAD_DIR=../uploads
|
||||||
|
|
||||||
|
# CORS 配置
|
||||||
|
CORS_ORIGIN=http://localhost:5173
|
||||||
8
biji-php/.gitignore
vendored
Normal file
8
biji-php/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/vendor/
|
||||||
|
/.env
|
||||||
|
/uploads/*
|
||||||
|
!/uploads/.gitkeep
|
||||||
|
.DS_Store
|
||||||
|
.idea/
|
||||||
|
*.log
|
||||||
|
composer.lock
|
||||||
130
biji-php/README.md
Normal file
130
biji-php/README.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
# 笔记管理系统 - PHP 后端
|
||||||
|
|
||||||
|
基于 PHP 7.4 的笔记管理系统后端,使用 Slim Framework 4 构建 RESTful API。
|
||||||
|
|
||||||
|
## 环境要求
|
||||||
|
|
||||||
|
- PHP 7.4(线程安全版)
|
||||||
|
- MySQL 5.7+
|
||||||
|
- Apache/Nginx
|
||||||
|
- Composer
|
||||||
|
|
||||||
|
## 安装步骤
|
||||||
|
|
||||||
|
### 1. 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd biji-php
|
||||||
|
composer install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 配置环境变量
|
||||||
|
|
||||||
|
复制 `.env.example` 为 `.env` 并修改配置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
编辑 `.env` 文件,配置数据库连接信息:
|
||||||
|
|
||||||
|
```
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_NAME=biji_db
|
||||||
|
DB_USER=root
|
||||||
|
DB_PASS=your_password
|
||||||
|
|
||||||
|
JWT_SECRET=your-secret-key-change-this
|
||||||
|
JWT_EXPIRE=86400
|
||||||
|
|
||||||
|
CORS_ORIGIN=http://localhost:5173
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 导入数据库
|
||||||
|
|
||||||
|
使用 `sql/mysql/all.sql` 文件创建数据库表结构。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mysql -u root -p biji_db < ../sql/mysql/all.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 配置 Web 服务器
|
||||||
|
|
||||||
|
#### Apache 配置
|
||||||
|
|
||||||
|
确保启用了 `mod_rewrite` 模块,并将网站根目录指向 `public` 目录。
|
||||||
|
|
||||||
|
#### Nginx 配置示例
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name your-domain.com;
|
||||||
|
root /path/to/biji-php/public;
|
||||||
|
index index.php;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.php?$query_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ \.php$ {
|
||||||
|
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
|
||||||
|
fastcgi_index index.php;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
|
include fastcgi_params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API 接口文档
|
||||||
|
|
||||||
|
### 用户接口 (`/api/user`)
|
||||||
|
|
||||||
|
- `POST /register` - 用户注册
|
||||||
|
- `POST /login` - 用户登录
|
||||||
|
- `POST /validate-token` - 验证 Token(需认证)
|
||||||
|
- `PUT /password` - 更新密码(需认证)
|
||||||
|
- `DELETE /deleteUser` - 删除用户(需认证)
|
||||||
|
|
||||||
|
### Markdown 接口 (`/api/markdown`)
|
||||||
|
|
||||||
|
- `GET /{id}` - 获取笔记内容
|
||||||
|
- `GET /` - 获取所有笔记
|
||||||
|
- `POST /updateMarkdown` - 更新笔记(需认证)
|
||||||
|
- `DELETE /{id}` - 删除笔记(需认证)
|
||||||
|
|
||||||
|
### 分组接口 (`/api/groupings`)
|
||||||
|
|
||||||
|
- `GET /` - 获取所有分组(需认证)
|
||||||
|
- `POST /` - 创建分组(需认证)
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
biji-php/
|
||||||
|
├── public/ # Web 根目录
|
||||||
|
│ ├── index.php # 入口文件
|
||||||
|
│ └── .htaccess # Apache 重写规则
|
||||||
|
├── src/ # 源代码
|
||||||
|
│ ├── Controllers/ # 控制器
|
||||||
|
│ ├── Models/ # 模型
|
||||||
|
│ ├── Middleware/ # 中间件
|
||||||
|
│ └── Utils/ # 工具类
|
||||||
|
├── config/ # 配置文件
|
||||||
|
├── uploads/ # 文件上传目录
|
||||||
|
├── vendor/ # Composer 依赖
|
||||||
|
└── composer.json # Composer 配置
|
||||||
|
```
|
||||||
|
|
||||||
|
## 开发说明
|
||||||
|
|
||||||
|
本项目遵循以下原则:
|
||||||
|
- PSR-4 自动加载
|
||||||
|
- RESTful API 设计
|
||||||
|
- JWT Token 认证
|
||||||
|
- MVC 架构模式
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
MIT License
|
||||||
23
biji-php/composer.json
Normal file
23
biji-php/composer.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "biji/php-backend",
|
||||||
|
"description": "笔记管理系统 PHP 后端",
|
||||||
|
"type": "project",
|
||||||
|
"require": {
|
||||||
|
"php": "^7.4",
|
||||||
|
"slim/slim": "^4.10",
|
||||||
|
"slim/psr7": "^1.6",
|
||||||
|
"php-di/php-di": "^6.4",
|
||||||
|
"firebase/php-jwt": "^6.3",
|
||||||
|
"vlucas/phpdotenv": "^5.5"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"App\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"platform": {
|
||||||
|
"php": "7.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
biji-php/config/database.php
Normal file
15
biji-php/config/database.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'host' => $_ENV['DB_HOST'] ?? 'localhost',
|
||||||
|
'port' => $_ENV['DB_PORT'] ?? '3306',
|
||||||
|
'database' => $_ENV['DB_NAME'] ?? 'biji_db',
|
||||||
|
'username' => $_ENV['DB_USER'] ?? 'root',
|
||||||
|
'password' => $_ENV['DB_PASS'] ?? '',
|
||||||
|
'charset' => 'utf8mb4',
|
||||||
|
'options' => [
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
PDO::ATTR_EMULATE_PREPARES => false,
|
||||||
|
]
|
||||||
|
];
|
||||||
62
biji-php/config/routes.php
Normal file
62
biji-php/config/routes.php
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Routing\RouteCollectorProxy;
|
||||||
|
use App\Controllers\UserController;
|
||||||
|
use App\Controllers\MarkdownController;
|
||||||
|
use App\Controllers\GroupingController;
|
||||||
|
use App\Middleware\AuthMiddleware;
|
||||||
|
|
||||||
|
// 根路径测试路由
|
||||||
|
$app->get('/', function ($request, $response) {
|
||||||
|
$data = [
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Biji PHP API is running',
|
||||||
|
'version' => '1.0.0',
|
||||||
|
'endpoints' => [
|
||||||
|
'POST /api/user/register' => '用户注册',
|
||||||
|
'POST /api/user/login' => '用户登录',
|
||||||
|
'GET /api/markdown' => '获取所有笔记',
|
||||||
|
'GET /api/markdown/{id}' => '获取单个笔记',
|
||||||
|
'GET /api/groupings' => '获取分组列表'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
$response->getBody()->write(json_encode($data, JSON_UNESCAPED_UNICODE));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 用户相关路由(无需认证)
|
||||||
|
$app->group('/api/user', function (RouteCollectorProxy $group) {
|
||||||
|
$group->post('/register', [UserController::class, 'register']);
|
||||||
|
$group->post('/login', [UserController::class, 'login']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 用户相关路由(需要认证)
|
||||||
|
$app->group('/api/user', function (RouteCollectorProxy $group) {
|
||||||
|
$group->post('/validate-token', [UserController::class, 'validateToken']);
|
||||||
|
$group->put('/password', [UserController::class, 'updatePassword']);
|
||||||
|
$group->delete('/deleteUser', [UserController::class, 'deleteUser']);
|
||||||
|
})->add(new AuthMiddleware());
|
||||||
|
|
||||||
|
// Markdown 相关路由
|
||||||
|
$app->group('/api/markdown', function (RouteCollectorProxy $group) {
|
||||||
|
$group->get('/{id}', [MarkdownController::class, 'getById']);
|
||||||
|
$group->get('', [MarkdownController::class, 'getAll']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Markdown 相关路由(需要认证)
|
||||||
|
$app->group('/api/markdown', function (RouteCollectorProxy $group) {
|
||||||
|
$group->post('/updateMarkdown', [MarkdownController::class, 'update']);
|
||||||
|
$group->delete('/{id}', [MarkdownController::class, 'delete']);
|
||||||
|
})->add(new AuthMiddleware());
|
||||||
|
|
||||||
|
// 分组相关路由(无需认证)
|
||||||
|
$app->group('/api/groupings', function (RouteCollectorProxy $group) {
|
||||||
|
$group->get('', [GroupingController::class, 'getAll']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 分组相关路由(需要认证)
|
||||||
|
$app->group('/api/groupings', function (RouteCollectorProxy $group) {
|
||||||
|
$group->post('', [GroupingController::class, 'create']);
|
||||||
|
$group->put('/{id}', [GroupingController::class, 'update']);
|
||||||
|
$group->delete('/{id}', [GroupingController::class, 'delete']);
|
||||||
|
})->add(new AuthMiddleware());
|
||||||
4
biji-php/public/.htaccess
Normal file
4
biji-php/public/.htaccess
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
RewriteEngine On
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule ^ index.php [QSA,L]
|
||||||
37
biji-php/public/index.php
Normal file
37
biji-php/public/index.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Factory\AppFactory;
|
||||||
|
use DI\Container;
|
||||||
|
use Dotenv\Dotenv;
|
||||||
|
|
||||||
|
require __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
// 加载环境变量
|
||||||
|
$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
|
||||||
|
$dotenv->load();
|
||||||
|
|
||||||
|
// 创建容器
|
||||||
|
$container = new Container();
|
||||||
|
AppFactory::setContainer($container);
|
||||||
|
|
||||||
|
// 创建应用
|
||||||
|
$app = AppFactory::create();
|
||||||
|
|
||||||
|
// 添加路由中间件(必须在路由定义之前)
|
||||||
|
$app->addRoutingMiddleware();
|
||||||
|
|
||||||
|
// 添加错误处理
|
||||||
|
$app->addErrorMiddleware(true, true, true);
|
||||||
|
|
||||||
|
// 添加 CORS 中间件
|
||||||
|
$app->add(new \App\Middleware\CorsMiddleware());
|
||||||
|
|
||||||
|
// 处理 OPTIONS 请求
|
||||||
|
$app->options('/{routes:.+}', function ($request, $response) {
|
||||||
|
return $response;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载路由
|
||||||
|
require __DIR__ . '/../config/routes.php';
|
||||||
|
|
||||||
|
$app->run();
|
||||||
42
biji-php/src/Controllers/GroupingController.php
Normal file
42
biji-php/src/Controllers/GroupingController.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controllers;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
|
use App\Models\Grouping;
|
||||||
|
use App\Utils\Response as ApiResponse;
|
||||||
|
|
||||||
|
class GroupingController
|
||||||
|
{
|
||||||
|
public function getAll(Request $request, Response $response)
|
||||||
|
{
|
||||||
|
$params = $request->getQueryParams();
|
||||||
|
$parentId = $params['parentId'] ?? null;
|
||||||
|
|
||||||
|
$model = new Grouping();
|
||||||
|
$groupings = $model->getAll($parentId);
|
||||||
|
|
||||||
|
$response->getBody()->write(json_encode(ApiResponse::success($groupings), JSON_UNESCAPED_UNICODE));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(Request $request, Response $response)
|
||||||
|
{
|
||||||
|
$data = $request->getParsedBody();
|
||||||
|
$grouping = $data['grouping'] ?? '';
|
||||||
|
$parentId = $data['parentId'] ?? 0;
|
||||||
|
|
||||||
|
if (empty($grouping)) {
|
||||||
|
$response->getBody()->write(json_encode(ApiResponse::fail('分组名称不能为空'), JSON_UNESCAPED_UNICODE));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$model = new Grouping();
|
||||||
|
$id = $model->create($grouping, $parentId);
|
||||||
|
$created = $model->findById($id);
|
||||||
|
|
||||||
|
$response->getBody()->write(json_encode(ApiResponse::success($created), JSON_UNESCAPED_UNICODE));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
|
||||||
|
}
|
||||||
|
}
|
||||||
76
biji-php/src/Controllers/MarkdownController.php
Normal file
76
biji-php/src/Controllers/MarkdownController.php
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controllers;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
|
use App\Models\MarkdownFile;
|
||||||
|
use App\Utils\Response as ApiResponse;
|
||||||
|
|
||||||
|
class MarkdownController
|
||||||
|
{
|
||||||
|
public function getById(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$id = $args['id'];
|
||||||
|
$model = new MarkdownFile();
|
||||||
|
$file = $model->findById($id);
|
||||||
|
|
||||||
|
if (!$file) {
|
||||||
|
$response->getBody()->write(ApiResponse::json(ApiResponse::fail('文件不存在')));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否为私密笔记
|
||||||
|
if ($file['is_private'] == 1) {
|
||||||
|
// 检查是否已认证
|
||||||
|
$userId = $request->getAttribute('userId');
|
||||||
|
if (!$userId) {
|
||||||
|
// 未认证,返回空内容
|
||||||
|
$response->getBody()->write(ApiResponse::json(ApiResponse::success('')));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$response->getBody()->write(ApiResponse::json(ApiResponse::success($file['content'])));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAll(Request $request, Response $response)
|
||||||
|
{
|
||||||
|
$model = new MarkdownFile();
|
||||||
|
$files = $model->getAll();
|
||||||
|
|
||||||
|
$response->getBody()->write(ApiResponse::json(ApiResponse::success($files)));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, Response $response)
|
||||||
|
{
|
||||||
|
$data = $request->getParsedBody();
|
||||||
|
$id = $data['id'] ?? null;
|
||||||
|
|
||||||
|
if (!$id) {
|
||||||
|
$response->getBody()->write(ApiResponse::json(ApiResponse::fail('缺少文件ID')));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$model = new MarkdownFile();
|
||||||
|
$model->update($id, $data);
|
||||||
|
|
||||||
|
$file = $model->findById($id);
|
||||||
|
$response->getBody()->write(ApiResponse::json(ApiResponse::success($file)));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$id = $args['id'];
|
||||||
|
$userId = $request->getAttribute('userId');
|
||||||
|
|
||||||
|
$model = new MarkdownFile();
|
||||||
|
$model->softDelete($id, $userId);
|
||||||
|
|
||||||
|
$response->getBody()->write(ApiResponse::json(ApiResponse::success(null, '删除成功')));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
|
||||||
|
}
|
||||||
|
}
|
||||||
137
biji-php/src/Controllers/UserController.php
Normal file
137
biji-php/src/Controllers/UserController.php
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controllers;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\SystemSetting;
|
||||||
|
use App\Models\RegistrationCode;
|
||||||
|
use App\Utils\JWTUtil;
|
||||||
|
use App\Utils\Response as ApiResponse;
|
||||||
|
|
||||||
|
class UserController
|
||||||
|
{
|
||||||
|
public function register(Request $request, Response $response)
|
||||||
|
{
|
||||||
|
$data = $request->getParsedBody();
|
||||||
|
$username = $data['username'] ?? '';
|
||||||
|
$password = $data['password'] ?? '';
|
||||||
|
$email = $data['email'] ?? '';
|
||||||
|
$registrationCode = $data['registrationCode'] ?? '';
|
||||||
|
|
||||||
|
// 验证输入
|
||||||
|
if (empty($username) || empty($password) || empty($email)) {
|
||||||
|
$response->getBody()->write(ApiResponse::json(ApiResponse::fail('用户名、密码和邮箱不能为空')));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查注册功能是否开启
|
||||||
|
$systemSetting = new SystemSetting();
|
||||||
|
if (!$systemSetting->isRegistrationEnabled()) {
|
||||||
|
$response->getBody()->write(ApiResponse::json(ApiResponse::fail('注册功能已关闭')));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证注册码
|
||||||
|
$regCodeModel = new RegistrationCode();
|
||||||
|
if (!$regCodeModel->validateCode($registrationCode)) {
|
||||||
|
$response->getBody()->write(ApiResponse::json(ApiResponse::fail('无效或已过期的注册码')));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$userModel = new User();
|
||||||
|
|
||||||
|
// 检查用户名是否已存在
|
||||||
|
if ($userModel->findByUsername($username)) {
|
||||||
|
$response->getBody()->write(ApiResponse::json(ApiResponse::fail('用户名已存在')));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$userId = $userModel->create($username, $password, $email);
|
||||||
|
$user = $userModel->findById($userId);
|
||||||
|
unset($user['password']); // 不返回密码
|
||||||
|
|
||||||
|
$response->getBody()->write(ApiResponse::json(ApiResponse::success($user, '注册成功')));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$response->getBody()->write(ApiResponse::json(ApiResponse::error('注册失败: ' . $e->getMessage())));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function login(Request $request, Response $response)
|
||||||
|
{
|
||||||
|
$data = $request->getParsedBody();
|
||||||
|
$username = $data['username'] ?? '';
|
||||||
|
$password = $data['password'] ?? '';
|
||||||
|
|
||||||
|
if (empty($username) || empty($password)) {
|
||||||
|
$response->getBody()->write(ApiResponse::json(ApiResponse::fail('用户名和密码不能为空')));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$userModel = new User();
|
||||||
|
$user = $userModel->findByUsername($username);
|
||||||
|
|
||||||
|
if (!$user || !$userModel->verifyPassword($password, $user['password'])) {
|
||||||
|
$response->getBody()->write(ApiResponse::json(ApiResponse::fail('用户名或密码错误')));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(401);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成 JWT token
|
||||||
|
$token = JWTUtil::encode($user['id'], $user['username']);
|
||||||
|
$expireTime = date('Y-m-d H:i:s', time() + ($_ENV['JWT_EXPIRE'] ?? 86400));
|
||||||
|
|
||||||
|
// 更新数据库中的 token
|
||||||
|
$userModel->updateToken($user['id'], $token, $expireTime);
|
||||||
|
|
||||||
|
$response->getBody()->write(ApiResponse::json(ApiResponse::success(['token' => $token], '登录成功')));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateToken(Request $request, Response $response)
|
||||||
|
{
|
||||||
|
// 如果能到达这里,说明 token 已经通过中间件验证
|
||||||
|
$response->getBody()->write(ApiResponse::json(ApiResponse::success(null, 'Token is valid')));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatePassword(Request $request, Response $response)
|
||||||
|
{
|
||||||
|
$userId = $request->getAttribute('userId');
|
||||||
|
$data = $request->getParsedBody();
|
||||||
|
$oldPassword = $data['oldPassword'] ?? '';
|
||||||
|
$newPassword = $data['newPassword'] ?? '';
|
||||||
|
|
||||||
|
if (empty($oldPassword) || empty($newPassword)) {
|
||||||
|
$response->getBody()->write(ApiResponse::json(ApiResponse::fail('旧密码和新密码不能为空')));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$userModel = new User();
|
||||||
|
$user = $userModel->findById($userId);
|
||||||
|
|
||||||
|
if (!$userModel->verifyPassword($oldPassword, $user['password'])) {
|
||||||
|
$response->getBody()->write(ApiResponse::json(ApiResponse::fail('旧密码错误')));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$userModel->updatePassword($userId, $newPassword);
|
||||||
|
|
||||||
|
$response->getBody()->write(ApiResponse::json(ApiResponse::success(null, '密码更新成功')));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteUser(Request $request, Response $response)
|
||||||
|
{
|
||||||
|
$userId = $request->getAttribute('userId');
|
||||||
|
|
||||||
|
$userModel = new User();
|
||||||
|
$userModel->delete($userId);
|
||||||
|
|
||||||
|
$response->getBody()->write(ApiResponse::json(ApiResponse::success(null, '用户删除成功')));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
|
||||||
|
}
|
||||||
|
}
|
||||||
47
biji-php/src/Middleware/AuthMiddleware.php
Normal file
47
biji-php/src/Middleware/AuthMiddleware.php
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Middleware;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
|
||||||
|
use Slim\Psr7\Response;
|
||||||
|
use App\Utils\JWTUtil;
|
||||||
|
|
||||||
|
class AuthMiddleware
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, RequestHandler $handler): Response
|
||||||
|
{
|
||||||
|
$authHeader = $request->getHeaderLine('Authorization');
|
||||||
|
|
||||||
|
if (empty($authHeader)) {
|
||||||
|
$response = new Response();
|
||||||
|
$response->getBody()->write(json_encode([
|
||||||
|
'code' => 401,
|
||||||
|
'message' => '未提供认证令牌',
|
||||||
|
'data' => null
|
||||||
|
]));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json')->withStatus(401);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取 token (Bearer token)
|
||||||
|
$token = str_replace('Bearer ', '', $authHeader);
|
||||||
|
|
||||||
|
$decoded = JWTUtil::decode($token);
|
||||||
|
|
||||||
|
if ($decoded === null) {
|
||||||
|
$response = new Response();
|
||||||
|
$response->getBody()->write(json_encode([
|
||||||
|
'code' => 401,
|
||||||
|
'message' => '无效或过期的令牌',
|
||||||
|
'data' => null
|
||||||
|
]));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json')->withStatus(401);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将用户信息添加到请求属性中
|
||||||
|
$request = $request->withAttribute('userId', $decoded['userId']);
|
||||||
|
$request = $request->withAttribute('username', $decoded['username']);
|
||||||
|
|
||||||
|
return $handler->handle($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
biji-php/src/Middleware/CorsMiddleware.php
Normal file
23
biji-php/src/Middleware/CorsMiddleware.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Middleware;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
|
||||||
|
use Slim\Psr7\Response;
|
||||||
|
|
||||||
|
class CorsMiddleware
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, RequestHandler $handler): Response
|
||||||
|
{
|
||||||
|
$response = $handler->handle($request);
|
||||||
|
|
||||||
|
$origin = $_ENV['CORS_ORIGIN'] ?? '*';
|
||||||
|
|
||||||
|
return $response
|
||||||
|
->withHeader('Access-Control-Allow-Origin', $origin)
|
||||||
|
->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization')
|
||||||
|
->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS')
|
||||||
|
->withHeader('Access-Control-Allow-Credentials', 'true');
|
||||||
|
}
|
||||||
|
}
|
||||||
57
biji-php/src/Models/Grouping.php
Normal file
57
biji-php/src/Models/Grouping.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Utils\Database;
|
||||||
|
use PDO;
|
||||||
|
|
||||||
|
class Grouping
|
||||||
|
{
|
||||||
|
private $db;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->db = Database::getInstance()->getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAll($parentId = null)
|
||||||
|
{
|
||||||
|
if ($parentId === null) {
|
||||||
|
$stmt = $this->db->prepare("SELECT * FROM `grouping` WHERE is_deleted = 0 ORDER BY id");
|
||||||
|
} else {
|
||||||
|
$stmt = $this->db->prepare("SELECT * FROM `grouping` WHERE parentId = ? AND is_deleted = 0 ORDER BY id");
|
||||||
|
$stmt->execute([$parentId]);
|
||||||
|
return $stmt->fetchAll();
|
||||||
|
}
|
||||||
|
$stmt->execute();
|
||||||
|
return $stmt->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findById($id)
|
||||||
|
{
|
||||||
|
$stmt = $this->db->prepare("SELECT * FROM `grouping` WHERE id = ? AND is_deleted = 0");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
return $stmt->fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create($grouping, $parentId = 0)
|
||||||
|
{
|
||||||
|
$stmt = $this->db->prepare("INSERT INTO `grouping` (`grouping`, parentId) VALUES (?, ?)");
|
||||||
|
$stmt->execute([$grouping, $parentId]);
|
||||||
|
return $this->db->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update($id, $grouping)
|
||||||
|
{
|
||||||
|
$stmt = $this->db->prepare("UPDATE `grouping` SET `grouping` = ? WHERE id = ?");
|
||||||
|
return $stmt->execute([$grouping, $id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function softDelete($id, $userId)
|
||||||
|
{
|
||||||
|
$stmt = $this->db->prepare(
|
||||||
|
"UPDATE `grouping` SET is_deleted = 1, deleted_at = NOW(), deleted_by = ? WHERE id = ?"
|
||||||
|
);
|
||||||
|
return $stmt->execute([$userId, $id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
122
biji-php/src/Models/MarkdownFile.php
Normal file
122
biji-php/src/Models/MarkdownFile.php
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Utils\Database;
|
||||||
|
use PDO;
|
||||||
|
|
||||||
|
class MarkdownFile
|
||||||
|
{
|
||||||
|
private $db;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->db = Database::getInstance()->getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findById($id, $includeDeleted = false)
|
||||||
|
{
|
||||||
|
$sql = "SELECT * FROM markdown_file WHERE id = ?";
|
||||||
|
if (!$includeDeleted) {
|
||||||
|
$sql .= " AND is_deleted = 0";
|
||||||
|
}
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
return $stmt->fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAll()
|
||||||
|
{
|
||||||
|
$stmt = $this->db->prepare("SELECT * FROM markdown_file WHERE is_deleted = 0 ORDER BY updated_at DESC");
|
||||||
|
$stmt->execute();
|
||||||
|
return $stmt->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getByGroupingId($groupingId)
|
||||||
|
{
|
||||||
|
$stmt = $this->db->prepare(
|
||||||
|
"SELECT id, title, file_name, created_at, updated_at, is_private, grouping_id
|
||||||
|
FROM markdown_file
|
||||||
|
WHERE grouping_id = ? AND is_deleted = 0
|
||||||
|
ORDER BY updated_at DESC"
|
||||||
|
);
|
||||||
|
$stmt->execute([$groupingId]);
|
||||||
|
return $stmt->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function searchByTitle($keyword)
|
||||||
|
{
|
||||||
|
$stmt = $this->db->prepare(
|
||||||
|
"SELECT * FROM markdown_file
|
||||||
|
WHERE title LIKE ? AND is_deleted = 0
|
||||||
|
ORDER BY updated_at DESC"
|
||||||
|
);
|
||||||
|
$stmt->execute(['%' . $keyword . '%']);
|
||||||
|
return $stmt->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRecent($limit = 12)
|
||||||
|
{
|
||||||
|
$stmt = $this->db->prepare(
|
||||||
|
"SELECT id, title, file_name, created_at, updated_at, is_private, grouping_id
|
||||||
|
FROM markdown_file
|
||||||
|
WHERE is_deleted = 0
|
||||||
|
ORDER BY updated_at DESC
|
||||||
|
LIMIT ?"
|
||||||
|
);
|
||||||
|
$stmt->execute([$limit]);
|
||||||
|
return $stmt->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update($id, $data)
|
||||||
|
{
|
||||||
|
$fields = [];
|
||||||
|
$values = [];
|
||||||
|
|
||||||
|
if (isset($data['title'])) {
|
||||||
|
$fields[] = "title = ?";
|
||||||
|
$values[] = $data['title'];
|
||||||
|
}
|
||||||
|
if (isset($data['content'])) {
|
||||||
|
$fields[] = "content = ?";
|
||||||
|
$values[] = $data['content'];
|
||||||
|
}
|
||||||
|
if (isset($data['grouping_id'])) {
|
||||||
|
$fields[] = "grouping_id = ?";
|
||||||
|
$values[] = $data['grouping_id'];
|
||||||
|
}
|
||||||
|
if (isset($data['is_private'])) {
|
||||||
|
$fields[] = "is_private = ?";
|
||||||
|
$values[] = $data['is_private'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$fields[] = "updated_at = NOW()";
|
||||||
|
$values[] = $id;
|
||||||
|
|
||||||
|
$sql = "UPDATE markdown_file SET " . implode(", ", $fields) . " WHERE id = ?";
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
return $stmt->execute($values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function softDelete($id, $userId)
|
||||||
|
{
|
||||||
|
$stmt = $this->db->prepare(
|
||||||
|
"UPDATE markdown_file SET is_deleted = 1, deleted_at = NOW(), deleted_by = ? WHERE id = ?"
|
||||||
|
);
|
||||||
|
return $stmt->execute([$userId, $id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function restore($id)
|
||||||
|
{
|
||||||
|
$stmt = $this->db->prepare(
|
||||||
|
"UPDATE markdown_file SET is_deleted = 0, deleted_at = NULL, deleted_by = NULL WHERE id = ?"
|
||||||
|
);
|
||||||
|
return $stmt->execute([$id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function permanentDelete($id)
|
||||||
|
{
|
||||||
|
$stmt = $this->db->prepare("DELETE FROM markdown_file WHERE id = ?");
|
||||||
|
return $stmt->execute([$id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
38
biji-php/src/Models/RegistrationCode.php
Normal file
38
biji-php/src/Models/RegistrationCode.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Utils\Database;
|
||||||
|
use PDO;
|
||||||
|
|
||||||
|
class RegistrationCode
|
||||||
|
{
|
||||||
|
private $db;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->db = Database::getInstance()->getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateCode($createdBy)
|
||||||
|
{
|
||||||
|
$code = bin2hex(random_bytes(16));
|
||||||
|
$expiryTime = date('Y-m-d H:i:s', strtotime('+7 days'));
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare(
|
||||||
|
"INSERT INTO registration_codes (code, expiry_time, created_by, created_at) VALUES (?, ?, ?, NOW())"
|
||||||
|
);
|
||||||
|
$stmt->execute([$code, $expiryTime, $createdBy]);
|
||||||
|
|
||||||
|
return $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateCode($code)
|
||||||
|
{
|
||||||
|
$stmt = $this->db->prepare(
|
||||||
|
"SELECT * FROM registration_codes WHERE code = ? AND expiry_time > NOW()"
|
||||||
|
);
|
||||||
|
$stmt->execute([$code]);
|
||||||
|
return $stmt->fetch() !== false;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
biji-php/src/Models/SystemSetting.php
Normal file
44
biji-php/src/Models/SystemSetting.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Utils\Database;
|
||||||
|
use PDO;
|
||||||
|
|
||||||
|
class SystemSetting
|
||||||
|
{
|
||||||
|
private $db;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->db = Database::getInstance()->getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSetting($key)
|
||||||
|
{
|
||||||
|
$stmt = $this->db->prepare("SELECT setting_value FROM system_settings WHERE setting_key = ?");
|
||||||
|
$stmt->execute([$key]);
|
||||||
|
$result = $stmt->fetch();
|
||||||
|
return $result ? $result['setting_value'] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSetting($key, $value)
|
||||||
|
{
|
||||||
|
$stmt = $this->db->prepare(
|
||||||
|
"INSERT INTO system_settings (setting_key, setting_value) VALUES (?, ?)
|
||||||
|
ON DUPLICATE KEY UPDATE setting_value = ?"
|
||||||
|
);
|
||||||
|
return $stmt->execute([$key, $value, $value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isRegistrationEnabled()
|
||||||
|
{
|
||||||
|
$value = $this->getSetting('registration_enabled');
|
||||||
|
return $value === '1' || $value === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRegistrationEnabled($enabled)
|
||||||
|
{
|
||||||
|
return $this->setSetting('registration_enabled', $enabled ? '1' : '0');
|
||||||
|
}
|
||||||
|
}
|
||||||
64
biji-php/src/Models/User.php
Normal file
64
biji-php/src/Models/User.php
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Utils\Database;
|
||||||
|
use PDO;
|
||||||
|
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
private $db;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->db = Database::getInstance()->getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findByUsername($username)
|
||||||
|
{
|
||||||
|
$stmt = $this->db->prepare("SELECT * FROM user WHERE username = ?");
|
||||||
|
$stmt->execute([$username]);
|
||||||
|
return $stmt->fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findById($id)
|
||||||
|
{
|
||||||
|
$stmt = $this->db->prepare("SELECT * FROM user WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
return $stmt->fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create($username, $password, $email)
|
||||||
|
{
|
||||||
|
$hashedPassword = password_hash($password, PASSWORD_BCRYPT);
|
||||||
|
$stmt = $this->db->prepare(
|
||||||
|
"INSERT INTO user (username, password, email, created_at, updated_at) VALUES (?, ?, ?, NOW(), NOW())"
|
||||||
|
);
|
||||||
|
$stmt->execute([$username, $hashedPassword, $email]);
|
||||||
|
return $this->db->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateToken($userId, $token, $expireTime)
|
||||||
|
{
|
||||||
|
$stmt = $this->db->prepare("UPDATE user SET token = ?, token_enddata = ? WHERE id = ?");
|
||||||
|
return $stmt->execute([$token, $expireTime, $userId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatePassword($userId, $newPassword)
|
||||||
|
{
|
||||||
|
$hashedPassword = password_hash($newPassword, PASSWORD_BCRYPT);
|
||||||
|
$stmt = $this->db->prepare("UPDATE user SET password = ?, updated_at = NOW() WHERE id = ?");
|
||||||
|
return $stmt->execute([$hashedPassword, $userId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete($userId)
|
||||||
|
{
|
||||||
|
$stmt = $this->db->prepare("DELETE FROM user WHERE id = ?");
|
||||||
|
return $stmt->execute([$userId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function verifyPassword($password, $hashedPassword)
|
||||||
|
{
|
||||||
|
return password_verify($password, $hashedPassword);
|
||||||
|
}
|
||||||
|
}
|
||||||
49
biji-php/src/Utils/Database.php
Normal file
49
biji-php/src/Utils/Database.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Utils;
|
||||||
|
|
||||||
|
use PDO;
|
||||||
|
use PDOException;
|
||||||
|
|
||||||
|
class Database
|
||||||
|
{
|
||||||
|
private static $instance = null;
|
||||||
|
private $connection;
|
||||||
|
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
$config = require __DIR__ . '/../../config/database.php';
|
||||||
|
|
||||||
|
$dsn = sprintf(
|
||||||
|
"mysql:host=%s;port=%s;dbname=%s;charset=%s",
|
||||||
|
$config['host'],
|
||||||
|
$config['port'],
|
||||||
|
$config['database'],
|
||||||
|
$config['charset']
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->connection = new PDO(
|
||||||
|
$dsn,
|
||||||
|
$config['username'],
|
||||||
|
$config['password'],
|
||||||
|
$config['options']
|
||||||
|
);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new \Exception("数据库连接失败: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getInstance()
|
||||||
|
{
|
||||||
|
if (self::$instance === null) {
|
||||||
|
self::$instance = new self();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConnection()
|
||||||
|
{
|
||||||
|
return $this->connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
biji-php/src/Utils/JWTUtil.php
Normal file
45
biji-php/src/Utils/JWTUtil.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Utils;
|
||||||
|
|
||||||
|
use Firebase\JWT\JWT;
|
||||||
|
use Firebase\JWT\Key;
|
||||||
|
|
||||||
|
class JWTUtil
|
||||||
|
{
|
||||||
|
private static $secret;
|
||||||
|
private static $expire;
|
||||||
|
|
||||||
|
public static function init()
|
||||||
|
{
|
||||||
|
self::$secret = $_ENV['JWT_SECRET'] ?? 'default-secret-key';
|
||||||
|
self::$expire = (int)($_ENV['JWT_EXPIRE'] ?? 86400);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function encode($userId, $username)
|
||||||
|
{
|
||||||
|
self::init();
|
||||||
|
|
||||||
|
$payload = [
|
||||||
|
'iss' => 'biji-php',
|
||||||
|
'iat' => time(),
|
||||||
|
'exp' => time() + self::$expire,
|
||||||
|
'userId' => $userId,
|
||||||
|
'username' => $username
|
||||||
|
];
|
||||||
|
|
||||||
|
return JWT::encode($payload, self::$secret, 'HS256');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function decode($token)
|
||||||
|
{
|
||||||
|
self::init();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$decoded = JWT::decode($token, new Key(self::$secret, 'HS256'));
|
||||||
|
return (array)$decoded;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
biji-php/src/Utils/Response.php
Normal file
41
biji-php/src/Utils/Response.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Utils;
|
||||||
|
|
||||||
|
class Response
|
||||||
|
{
|
||||||
|
public static function success($data = null, $message = '操作成功')
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'code' => 200,
|
||||||
|
'message' => $message,
|
||||||
|
'data' => $data
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fail($message = '操作失败', $code = 400)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'code' => $code,
|
||||||
|
'message' => $message,
|
||||||
|
'data' => null
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function error($message = '服务器错误', $code = 500)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'code' => $code,
|
||||||
|
'message' => $message,
|
||||||
|
'data' => null
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将数组转换为 JSON 字符串(中文不转义)
|
||||||
|
*/
|
||||||
|
public static function json($data)
|
||||||
|
{
|
||||||
|
return json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
}
|
||||||
0
biji-php/uploads/.gitkeep
Normal file
0
biji-php/uploads/.gitkeep
Normal file
172
sql/mysql/biji_db.sql
Normal file
172
sql/mysql/biji_db.sql
Normal file
File diff suppressed because one or more lines are too long
BIN
uploads/1fc870d6-d162-4ac3-812a-d862b930576c.png
Normal file
BIN
uploads/1fc870d6-d162-4ac3-812a-d862b930576c.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
Reference in New Issue
Block a user