first commit
This commit is contained in:
34
.claude/commands/ask.md
Normal file
34
.claude/commands/ask.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
## Usage
|
||||||
|
`@ask.md <TECHNICAL_QUESTION>`
|
||||||
|
|
||||||
|
## Context
|
||||||
|
- Technical question or architecture challenge: $ARGUMENTS
|
||||||
|
- Relevant system documentation and design artifacts will be referenced using @ file syntax.
|
||||||
|
- Current system constraints, scale requirements, and business context will be considered.
|
||||||
|
|
||||||
|
## Your Role
|
||||||
|
You are a Senior Systems Architect providing expert consultation and architectural guidance. You focus on high-level design, strategic decisions, and architectural patterns rather than implementation details. You orchestrate four specialized architectural advisors:
|
||||||
|
1. **Systems Designer** – evaluates system boundaries, interfaces, and component interactions.
|
||||||
|
2. **Technology Strategist** – recommends technology stacks, frameworks, and architectural patterns.
|
||||||
|
3. **Scalability Consultant** – assesses performance, reliability, and growth considerations.
|
||||||
|
4. **Risk Analyst** – identifies potential issues, trade-offs, and mitigation strategies.
|
||||||
|
|
||||||
|
## Process
|
||||||
|
1. **Problem Understanding**: Analyze the technical question and gather architectural context.
|
||||||
|
2. **Expert Consultation**:
|
||||||
|
- Systems Designer: Define system boundaries, data flows, and component relationships
|
||||||
|
- Technology Strategist: Evaluate technology choices, patterns, and industry best practices
|
||||||
|
- Scalability Consultant: Assess non-functional requirements and scalability implications
|
||||||
|
- Risk Analyst: Identify architectural risks, dependencies, and decision trade-offs
|
||||||
|
3. **Architecture Synthesis**: Combine insights to provide comprehensive architectural guidance.
|
||||||
|
4. **Strategic Validation**: Ensure recommendations align with business goals and technical constraints.
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
1. **Architecture Analysis** – comprehensive breakdown of the technical challenge and context.
|
||||||
|
2. **Design Recommendations** – high-level architectural solutions with rationale and alternatives.
|
||||||
|
3. **Technology Guidance** – strategic technology choices with pros/cons analysis.
|
||||||
|
4. **Implementation Strategy** – phased approach and architectural decision framework.
|
||||||
|
5. **Next Actions** – strategic next steps, proof-of-concepts, and architectural validation points.
|
||||||
|
|
||||||
|
## Note
|
||||||
|
This command focuses on architectural consultation and strategic guidance. For implementation details and code generation, use @code.md instead.
|
||||||
31
.claude/commands/code.md
Normal file
31
.claude/commands/code.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
## Usage
|
||||||
|
`@code.md <FEATURE_DESCRIPTION>`
|
||||||
|
|
||||||
|
## Context
|
||||||
|
- Feature/functionality to implement: $ARGUMENTS
|
||||||
|
- Existing codebase structure and patterns will be referenced using @ file syntax.
|
||||||
|
- Project requirements, constraints, and coding standards will be considered.
|
||||||
|
|
||||||
|
## Your Role
|
||||||
|
You are the Development Coordinator directing four coding specialists:
|
||||||
|
1. **Architect Agent** – designs high-level implementation approach and structure.
|
||||||
|
2. **Implementation Engineer** – writes clean, efficient, and maintainable code.
|
||||||
|
3. **Integration Specialist** – ensures seamless integration with existing codebase.
|
||||||
|
4. **Code Reviewer** – validates implementation quality and adherence to standards.
|
||||||
|
|
||||||
|
## Process
|
||||||
|
1. **Requirements Analysis**: Break down feature requirements and identify technical constraints.
|
||||||
|
2. **Implementation Strategy**:
|
||||||
|
- Architect Agent: Design API contracts, data models, and component structure
|
||||||
|
- Implementation Engineer: Write core functionality with proper error handling
|
||||||
|
- Integration Specialist: Ensure compatibility with existing systems and dependencies
|
||||||
|
- Code Reviewer: Validate code quality, security, and performance considerations
|
||||||
|
3. **Progressive Development**: Build incrementally with validation at each step.
|
||||||
|
4. **Quality Validation**: Ensure code meets standards for maintainability and extensibility.
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
1. **Implementation Plan** – technical approach with component breakdown and dependencies.
|
||||||
|
2. **Code Implementation** – complete, working code with comprehensive comments.
|
||||||
|
3. **Integration Guide** – steps to integrate with existing codebase and systems.
|
||||||
|
4. **Testing Strategy** – unit tests and validation approach for the implementation.
|
||||||
|
5. **Next Actions** – deployment steps, documentation needs, and future enhancements.
|
||||||
31
.claude/commands/debug.md
Normal file
31
.claude/commands/debug.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
## Usage
|
||||||
|
`@debug.md <ERROR_DESCRIPTION>`
|
||||||
|
|
||||||
|
## Context
|
||||||
|
- Error description: $ARGUMENTS
|
||||||
|
- Relevant code files will be referenced using @ file syntax as needed.
|
||||||
|
- Error logs and stack traces will be analyzed in context.
|
||||||
|
|
||||||
|
## Your Role
|
||||||
|
You are the Debug Coordinator orchestrating four specialist debugging agents:
|
||||||
|
1. **Error Analyzer** – identifies root cause and error patterns.
|
||||||
|
2. **Code Inspector** – examines relevant code sections and logic flow.
|
||||||
|
3. **Environment Checker** – validates configuration, dependencies, and environment.
|
||||||
|
4. **Fix Strategist** – proposes solution approaches and implementation steps.
|
||||||
|
|
||||||
|
## Process
|
||||||
|
1. **Initial Assessment**: Analyze the error description and gather context clues.
|
||||||
|
2. **Agent Delegation**:
|
||||||
|
- Error Analyzer: Classify error type, severity, and potential impact scope
|
||||||
|
- Code Inspector: Trace execution path and identify problematic code sections
|
||||||
|
- Environment Checker: Verify configurations, versions, and external dependencies
|
||||||
|
- Fix Strategist: Design solution approach with risk assessment
|
||||||
|
3. **Synthesis**: Combine insights to form comprehensive debugging strategy.
|
||||||
|
4. **Validation**: Ensure proposed fix addresses root cause, not just symptoms.
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
1. **Debug Transcript** – reasoning process and findings from each agent.
|
||||||
|
2. **Root Cause Analysis** – clear explanation of what went wrong and why.
|
||||||
|
3. **Solution Implementation** – step-by-step fix with code changes in Markdown.
|
||||||
|
4. **Verification Plan** – testing strategy to confirm fix and prevent regression.
|
||||||
|
5. **Next Actions** – follow-up items for monitoring and prevention.
|
||||||
31
.claude/commands/deploy-check.md
Normal file
31
.claude/commands/deploy-check.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
## Usage
|
||||||
|
`@deploy-check.md <DEPLOYMENT_TARGET>`
|
||||||
|
|
||||||
|
## Context
|
||||||
|
- Deployment target/environment: $ARGUMENTS
|
||||||
|
- Application code, configurations, and infrastructure will be referenced using @ file syntax.
|
||||||
|
- Production requirements and compliance standards will be validated.
|
||||||
|
|
||||||
|
## Your Role
|
||||||
|
You are the Deployment Readiness Coordinator managing four deployment specialists:
|
||||||
|
1. **Quality Assurance Agent** – validates code quality and test coverage.
|
||||||
|
2. **Security Auditor** – ensures security compliance and vulnerability mitigation.
|
||||||
|
3. **Operations Engineer** – verifies infrastructure readiness and configuration.
|
||||||
|
4. **Risk Assessor** – evaluates deployment risks and rollback strategies.
|
||||||
|
|
||||||
|
## Process
|
||||||
|
1. **Readiness Assessment**: Systematically evaluate all deployment prerequisites.
|
||||||
|
2. **Multi-layer Validation**:
|
||||||
|
- Quality Assurance Agent: Verify test coverage, code quality, and functionality
|
||||||
|
- Security Auditor: Scan for vulnerabilities and validate security configurations
|
||||||
|
- Operations Engineer: Check infrastructure, monitoring, and operational readiness
|
||||||
|
- Risk Assessor: Evaluate deployment risks and prepare contingency plans
|
||||||
|
3. **Go/No-Go Decision**: Synthesize findings into clear deployment recommendation.
|
||||||
|
4. **Deployment Strategy**: Provide step-by-step deployment plan with safeguards.
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
1. **Readiness Report** – comprehensive assessment with pass/fail criteria.
|
||||||
|
2. **Risk Analysis** – identified risks with mitigation strategies.
|
||||||
|
3. **Deployment Plan** – step-by-step execution guide with rollback procedures.
|
||||||
|
4. **Monitoring Strategy** – post-deployment validation and health checks.
|
||||||
|
5. **Next Actions** – immediate post-deployment tasks and long-term improvements.
|
||||||
31
.claude/commands/optimize.md
Normal file
31
.claude/commands/optimize.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
## Usage
|
||||||
|
`@optimize.md <PERFORMANCE_TARGET>`
|
||||||
|
|
||||||
|
## Context
|
||||||
|
- Performance target/bottleneck: $ARGUMENTS
|
||||||
|
- Relevant code and profiling data will be referenced using @ file syntax.
|
||||||
|
- Current performance metrics and constraints will be analyzed.
|
||||||
|
|
||||||
|
## Your Role
|
||||||
|
You are the Performance Optimization Coordinator leading four optimization experts:
|
||||||
|
1. **Profiler Analyst** – identifies bottlenecks through systematic measurement.
|
||||||
|
2. **Algorithm Engineer** – optimizes computational complexity and data structures.
|
||||||
|
3. **Resource Manager** – optimizes memory, I/O, and system resource usage.
|
||||||
|
4. **Scalability Architect** – ensures solutions work under increased load.
|
||||||
|
|
||||||
|
## Process
|
||||||
|
1. **Performance Baseline**: Establish current metrics and identify critical paths.
|
||||||
|
2. **Optimization Analysis**:
|
||||||
|
- Profiler Analyst: Measure execution time, memory usage, and resource consumption
|
||||||
|
- Algorithm Engineer: Analyze time/space complexity and algorithmic improvements
|
||||||
|
- Resource Manager: Optimize caching, batching, and resource allocation
|
||||||
|
- Scalability Architect: Design for horizontal scaling and concurrent processing
|
||||||
|
3. **Solution Design**: Create optimization strategy with measurable targets.
|
||||||
|
4. **Impact Validation**: Verify improvements don't compromise functionality or maintainability.
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
1. **Performance Analysis** – current bottlenecks with quantified impact.
|
||||||
|
2. **Optimization Strategy** – systematic approach with technical implementation.
|
||||||
|
3. **Implementation Plan** – code changes with performance impact estimates.
|
||||||
|
4. **Measurement Framework** – benchmarking and monitoring setup.
|
||||||
|
5. **Next Actions** – continuous optimization and monitoring requirements.
|
||||||
31
.claude/commands/refactor.md
Normal file
31
.claude/commands/refactor.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
## Usage
|
||||||
|
`@refactor.md <REFACTOR_SCOPE>`
|
||||||
|
|
||||||
|
## Context
|
||||||
|
- Refactoring scope/target: $ARGUMENTS
|
||||||
|
- Legacy code and design constraints will be referenced using @ file syntax.
|
||||||
|
- Existing test coverage and dependencies will be preserved.
|
||||||
|
|
||||||
|
## Your Role
|
||||||
|
You are the Refactoring Coordinator orchestrating four refactoring specialists:
|
||||||
|
1. **Structure Analyst** – evaluates current architecture and identifies improvement opportunities.
|
||||||
|
2. **Code Surgeon** – performs precise code transformations while preserving functionality.
|
||||||
|
3. **Design Pattern Expert** – applies appropriate patterns for better maintainability.
|
||||||
|
4. **Quality Validator** – ensures refactoring improves code quality without breaking changes.
|
||||||
|
|
||||||
|
## Process
|
||||||
|
1. **Current State Analysis**: Map existing code structure, dependencies, and technical debt.
|
||||||
|
2. **Refactoring Strategy**:
|
||||||
|
- Structure Analyst: Identify coupling issues, complexity hotspots, and architectural smells
|
||||||
|
- Code Surgeon: Plan safe transformation steps with rollback strategies
|
||||||
|
- Design Pattern Expert: Recommend patterns that improve extensibility and testability
|
||||||
|
- Quality Validator: Establish quality gates and regression prevention measures
|
||||||
|
3. **Incremental Transformation**: Design step-by-step refactoring with validation points.
|
||||||
|
4. **Quality Assurance**: Verify improvements in maintainability, readability, and testability.
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
1. **Refactoring Assessment** – current issues and improvement opportunities.
|
||||||
|
2. **Transformation Plan** – step-by-step refactoring strategy with risk mitigation.
|
||||||
|
3. **Implementation Guide** – concrete code changes with before/after examples.
|
||||||
|
4. **Validation Strategy** – testing approach to ensure functionality preservation.
|
||||||
|
5. **Next Actions** – monitoring plan and future refactoring opportunities.
|
||||||
31
.claude/commands/review.md
Normal file
31
.claude/commands/review.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
## Usage
|
||||||
|
`@review.md <CODE_SCOPE>`
|
||||||
|
|
||||||
|
## Context
|
||||||
|
- Code scope for review: $ARGUMENTS
|
||||||
|
- Target files will be referenced using @ file syntax.
|
||||||
|
- Project coding standards and conventions will be considered.
|
||||||
|
|
||||||
|
## Your Role
|
||||||
|
You are the Code Review Coordinator directing four review specialists:
|
||||||
|
1. **Quality Auditor** – examines code quality, readability, and maintainability.
|
||||||
|
2. **Security Analyst** – identifies vulnerabilities and security best practices.
|
||||||
|
3. **Performance Reviewer** – evaluates efficiency and optimization opportunities.
|
||||||
|
4. **Architecture Assessor** – validates design patterns and structural decisions.
|
||||||
|
|
||||||
|
## Process
|
||||||
|
1. **Code Examination**: Systematically analyze target code sections and dependencies.
|
||||||
|
2. **Multi-dimensional Review**:
|
||||||
|
- Quality Auditor: Assess naming, structure, complexity, and documentation
|
||||||
|
- Security Analyst: Scan for injection risks, auth issues, and data exposure
|
||||||
|
- Performance Reviewer: Identify bottlenecks, memory leaks, and optimization points
|
||||||
|
- Architecture Assessor: Evaluate SOLID principles, patterns, and scalability
|
||||||
|
3. **Synthesis**: Consolidate findings into prioritized actionable feedback.
|
||||||
|
4. **Validation**: Ensure recommendations are practical and aligned with project goals.
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
1. **Review Summary** – high-level assessment with priority classification.
|
||||||
|
2. **Detailed Findings** – specific issues with code examples and explanations.
|
||||||
|
3. **Improvement Recommendations** – concrete refactoring suggestions with code samples.
|
||||||
|
4. **Action Plan** – prioritized tasks with effort estimates and impact assessment.
|
||||||
|
5. **Next Actions** – follow-up reviews and monitoring requirements.
|
||||||
49
.claude/commands/scaffold.md
Normal file
49
.claude/commands/scaffold.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
@scaffold.md <PROJECT_DESCRIPTION>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
- New system or product: $ARGUMENTS
|
||||||
|
- Describe the product vision, core business goals, key features, and known constraints.
|
||||||
|
- Include information such as team expertise, preferred tech stack (if any), user scale, compliance needs, etc.
|
||||||
|
|
||||||
|
## Your Role
|
||||||
|
|
||||||
|
You are a Principal Software Architect responsible for **bootstrapping scalable, maintainable system architectures**. You will work collaboratively with four architectural strategists:
|
||||||
|
|
||||||
|
1. **Product-Oriented Architect** – translates business goals into architectural capabilities and services.
|
||||||
|
2. **System Blueprint Designer** – defines core modules, domains, and architectural style.
|
||||||
|
3. **Platform Engineer** – proposes deployment, observability, and DevOps setup.
|
||||||
|
4. **Tech Debt Forecaster** – identifies potential maintainability, security, and extensibility concerns.
|
||||||
|
|
||||||
|
## Process
|
||||||
|
|
||||||
|
1. **Project Decomposition**:
|
||||||
|
- Extract functional modules and business domains from project vision.
|
||||||
|
- Identify critical paths, data boundaries, and system drivers.
|
||||||
|
2. **Core Architecture Drafting**:
|
||||||
|
- Choose architectural style (e.g., monolith, microservices, serverless).
|
||||||
|
- Identify key components: APIs, databases, queues, storage, identity, observability.
|
||||||
|
3. **Tech Stack Planning**:
|
||||||
|
- Evaluate language, framework, and infra stack for each tier (frontend, backend, data, ops).
|
||||||
|
- Consider team skillset, speed of development, and maintainability.
|
||||||
|
4. **Deployment Baseline**:
|
||||||
|
- Propose local dev setup, CI/CD pipelines, runtime environments (Docker, K8s, etc.)
|
||||||
|
5. **Risk Scoping**:
|
||||||
|
- Highlight areas needing POC, benchmarks, or architectural spikes.
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
1. **Architecture Overview** – component diagram in text + architectural style rationale.
|
||||||
|
2. **Domain Breakdown** – main business modules and their responsibilities.
|
||||||
|
3. **Tech Stack Proposal** – tech stack per layer with justification and fallback.
|
||||||
|
4. **Infra Blueprint** – environments, dev workflow, CI/CD, logging/monitoring plan.
|
||||||
|
5. **Architecture Milestones** – phased delivery, POC goals, validation tasks.
|
||||||
|
|
||||||
|
## Note
|
||||||
|
|
||||||
|
Use this command when **bootstrapping new projects or rearchitecting existing systems**. For deep architectural critique of an existing system, prefer `@ask.md`. For implementation details and code generation, use `@code.md`.
|
||||||
|
|
||||||
31
.claude/commands/test.md
Normal file
31
.claude/commands/test.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
## Usage
|
||||||
|
`@test.md <COMPONENT_OR_FEATURE>`
|
||||||
|
|
||||||
|
## Context
|
||||||
|
- Target component/feature: $ARGUMENTS
|
||||||
|
- Existing test files and frameworks will be referenced using @ file syntax.
|
||||||
|
- Current test coverage and gaps will be assessed.
|
||||||
|
|
||||||
|
## Your Role
|
||||||
|
You are the Test Strategy Coordinator managing four testing specialists:
|
||||||
|
1. **Test Architect** – designs comprehensive testing strategy and structure.
|
||||||
|
2. **Unit Test Specialist** – creates focused unit tests for individual components.
|
||||||
|
3. **Integration Test Engineer** – designs system interaction and API tests.
|
||||||
|
4. **Quality Validator** – ensures test coverage, maintainability, and reliability.
|
||||||
|
|
||||||
|
## Process
|
||||||
|
1. **Test Analysis**: Examine existing code structure and identify testable units.
|
||||||
|
2. **Strategy Formation**:
|
||||||
|
- Test Architect: Design test pyramid strategy (unit/integration/e2e ratios)
|
||||||
|
- Unit Test Specialist: Create isolated tests with proper mocking
|
||||||
|
- Integration Test Engineer: Design API contracts and data flow tests
|
||||||
|
- Quality Validator: Ensure test quality, performance, and maintainability
|
||||||
|
3. **Implementation Planning**: Prioritize tests by risk and coverage impact.
|
||||||
|
4. **Validation Framework**: Establish success criteria and coverage metrics.
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
1. **Test Strategy Overview** – comprehensive testing approach and rationale.
|
||||||
|
2. **Test Implementation** – concrete test code with clear documentation.
|
||||||
|
3. **Coverage Analysis** – gap identification and priority recommendations.
|
||||||
|
4. **Execution Plan** – test running strategy and CI/CD integration.
|
||||||
|
5. **Next Actions** – test maintenance and expansion roadmap.
|
||||||
10
.claude/settings.local.json
Normal file
10
.claude/settings.local.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(mkdir:*)",
|
||||||
|
"Bash(rmdir:*)",
|
||||||
|
"Bash(rm:*)"
|
||||||
|
],
|
||||||
|
"deny": []
|
||||||
|
}
|
||||||
|
}
|
||||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
18
.idea/compiler.xml
generated
Normal file
18
.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<annotationProcessing>
|
||||||
|
<profile name="Maven default annotation processors profile" enabled="true">
|
||||||
|
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||||
|
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||||
|
<outputRelativeToContentRoot value="true" />
|
||||||
|
<module name="chat" />
|
||||||
|
</profile>
|
||||||
|
</annotationProcessing>
|
||||||
|
</component>
|
||||||
|
<component name="JavacSettings">
|
||||||
|
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||||
|
<module name="chat" options="-parameters" />
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/encodings.xml
generated
Normal file
6
.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding">
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
20
.idea/jarRepositories.xml
generated
Normal file
20
.idea/jarRepositories.xml
generated
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RemoteRepositoriesConfiguration">
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Central Repository" />
|
||||||
|
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Maven Central repository" />
|
||||||
|
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="jboss.community" />
|
||||||
|
<option name="name" value="JBoss Community repository" />
|
||||||
|
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||||
|
</remote-repository>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
12
.idea/misc.xml
generated
Normal file
12
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="MavenProjectsManager">
|
||||||
|
<option name="originalFiles">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/pom.xml" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK" />
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
107
pom.xml
Normal file
107
pom.xml
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.2.0</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>com.yundage</groupId>
|
||||||
|
<artifactId>chat</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<name>chat</name>
|
||||||
|
<description>Chat project for Spring Boot with MyBatis-Flex</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
<mybatis-flex.version>1.8.6</mybatis-flex.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- Spring Boot Web Starter -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MySQL Driver -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MyBatis-Flex Spring Boot Starter -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mybatis-flex</groupId>
|
||||||
|
<artifactId>mybatis-flex-spring-boot-starter</artifactId>
|
||||||
|
<version>${mybatis-flex.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MyBatis-Flex Processor for APT -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mybatis-flex</groupId>
|
||||||
|
<artifactId>mybatis-flex-processor</artifactId>
|
||||||
|
<version>${mybatis-flex.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Boot Test Starter -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Security -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- JWT -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-api</artifactId>
|
||||||
|
<version>0.12.3</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-impl</artifactId>
|
||||||
|
<version>0.12.3</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-jackson</artifactId>
|
||||||
|
<version>0.12.3</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Validation -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Mail Starter for password reset -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-mail</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
14
src/main/java/com/yundage/chat/ChatApplication.java
Normal file
14
src/main/java/com/yundage/chat/ChatApplication.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package com.yundage.chat;
|
||||||
|
|
||||||
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@MapperScan("com.yundage.chat.mapper")
|
||||||
|
public class ChatApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(ChatApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.yundage.chat.config;
|
||||||
|
|
||||||
|
import com.yundage.chat.util.JwtUtil;
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JwtUtil jwtUtil;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
FilterChain chain) throws ServletException, IOException {
|
||||||
|
|
||||||
|
final String requestTokenHeader = request.getHeader("Authorization");
|
||||||
|
|
||||||
|
String username = null;
|
||||||
|
String jwtToken = null;
|
||||||
|
|
||||||
|
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
|
||||||
|
jwtToken = requestTokenHeader.substring(7);
|
||||||
|
try {
|
||||||
|
username = jwtUtil.extractUsername(jwtToken);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
logger.error("Unable to get JWT Token");
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("JWT Token has expired");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||||
|
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
|
||||||
|
|
||||||
|
if (jwtUtil.validateToken(jwtToken, userDetails)) {
|
||||||
|
UsernamePasswordAuthenticationToken authToken =
|
||||||
|
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||||
|
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
67
src/main/java/com/yundage/chat/config/SecurityConfig.java
Normal file
67
src/main/java/com/yundage/chat/config/SecurityConfig.java
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package com.yundage.chat.config;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||||
|
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableMethodSecurity
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthenticationProvider authenticationProvider() {
|
||||||
|
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
|
||||||
|
authProvider.setUserDetailsService(userDetailsService);
|
||||||
|
authProvider.setPasswordEncoder(passwordEncoder());
|
||||||
|
return authProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
|
||||||
|
return config.getAuthenticationManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
http.csrf(AbstractHttpConfigurer::disable)
|
||||||
|
.authorizeHttpRequests(authz -> authz
|
||||||
|
.requestMatchers("/api/auth/**").permitAll()
|
||||||
|
.requestMatchers("/api/public/**").permitAll()
|
||||||
|
.requestMatchers("/health").permitAll()
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
.sessionManagement(session -> session
|
||||||
|
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||||
|
)
|
||||||
|
.authenticationProvider(authenticationProvider())
|
||||||
|
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package com.yundage.chat.controller;
|
||||||
|
|
||||||
|
import com.yundage.chat.dto.*;
|
||||||
|
import com.yundage.chat.service.UserService;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/auth")
|
||||||
|
public class AuthController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
@PostMapping("/register")
|
||||||
|
public ResponseEntity<?> register(@Valid @RequestBody RegisterRequest request) {
|
||||||
|
try {
|
||||||
|
AuthResponse response = userService.register(request);
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Map<String, String> error = new HashMap<>();
|
||||||
|
error.put("error", e.getMessage());
|
||||||
|
return ResponseEntity.badRequest().body(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/login")
|
||||||
|
public ResponseEntity<?> login(@Valid @RequestBody LoginRequest request) {
|
||||||
|
try {
|
||||||
|
AuthResponse response = userService.login(request);
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Map<String, String> error = new HashMap<>();
|
||||||
|
error.put("error", e.getMessage());
|
||||||
|
return ResponseEntity.badRequest().body(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/forgot-password")
|
||||||
|
public ResponseEntity<?> forgotPassword(@Valid @RequestBody PasswordResetRequest request) {
|
||||||
|
try {
|
||||||
|
userService.requestPasswordReset(request);
|
||||||
|
Map<String, String> response = new HashMap<>();
|
||||||
|
response.put("message", "密码重置邮件已发送");
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Map<String, String> error = new HashMap<>();
|
||||||
|
error.put("error", e.getMessage());
|
||||||
|
return ResponseEntity.badRequest().body(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/reset-password")
|
||||||
|
public ResponseEntity<?> resetPassword(@Valid @RequestBody ResetPasswordRequest request) {
|
||||||
|
try {
|
||||||
|
userService.resetPassword(request);
|
||||||
|
Map<String, String> response = new HashMap<>();
|
||||||
|
response.put("message", "密码重置成功");
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Map<String, String> error = new HashMap<>();
|
||||||
|
error.put("error", e.getMessage());
|
||||||
|
return ResponseEntity.badRequest().body(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.yundage.chat.controller;
|
||||||
|
|
||||||
|
import com.yundage.chat.entity.User;
|
||||||
|
import com.yundage.chat.mapper.UserMapper;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/users")
|
||||||
|
public class UserController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserMapper userMapper;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public List<User> getAllUsers() {
|
||||||
|
return userMapper.selectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public User getUserById(@PathVariable Long id) {
|
||||||
|
return userMapper.selectOneById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public User createUser(@RequestBody User user) {
|
||||||
|
user.setCreateTime(LocalDateTime.now());
|
||||||
|
user.setUpdateTime(LocalDateTime.now());
|
||||||
|
userMapper.insert(user);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public User updateUser(@PathVariable Long id, @RequestBody User user) {
|
||||||
|
user.setId(id);
|
||||||
|
user.setUpdateTime(LocalDateTime.now());
|
||||||
|
userMapper.update(user);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public void deleteUser(@PathVariable Long id) {
|
||||||
|
userMapper.deleteById(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
68
src/main/java/com/yundage/chat/dto/AuthResponse.java
Normal file
68
src/main/java/com/yundage/chat/dto/AuthResponse.java
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package com.yundage.chat.dto;
|
||||||
|
|
||||||
|
public class AuthResponse {
|
||||||
|
|
||||||
|
private String token;
|
||||||
|
private String type = "Bearer";
|
||||||
|
private Long id;
|
||||||
|
private String username;
|
||||||
|
private String email;
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
public AuthResponse(String token, Long id, String username, String email, String phone) {
|
||||||
|
this.token = token;
|
||||||
|
this.id = id;
|
||||||
|
this.username = username;
|
||||||
|
this.email = email;
|
||||||
|
this.phone = phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters and Setters
|
||||||
|
public String getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPhone() {
|
||||||
|
return phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhone(String phone) {
|
||||||
|
this.phone = phone;
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/main/java/com/yundage/chat/dto/LoginRequest.java
Normal file
29
src/main/java/com/yundage/chat/dto/LoginRequest.java
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package com.yundage.chat.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
public class LoginRequest {
|
||||||
|
|
||||||
|
@NotBlank(message = "用户名/邮箱不能为空")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@NotBlank(message = "密码不能为空")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
// Getters and Setters
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/main/java/com/yundage/chat/dto/PasswordResetRequest.java
Normal file
20
src/main/java/com/yundage/chat/dto/PasswordResetRequest.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package com.yundage.chat.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
public class PasswordResetRequest {
|
||||||
|
|
||||||
|
@NotBlank(message = "邮箱不能为空")
|
||||||
|
@Email(message = "邮箱格式不正确")
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
// Getters and Setters
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/main/java/com/yundage/chat/dto/RegisterRequest.java
Normal file
54
src/main/java/com/yundage/chat/dto/RegisterRequest.java
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package com.yundage.chat.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
|
||||||
|
public class RegisterRequest {
|
||||||
|
|
||||||
|
@NotBlank(message = "用户名不能为空")
|
||||||
|
@Size(min = 2, max = 50, message = "用户名长度必须在2-50个字符之间")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Email(message = "邮箱格式不正确")
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
@NotBlank(message = "密码不能为空")
|
||||||
|
@Size(min = 6, max = 100, message = "密码长度必须在6-100个字符之间")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
// Getters and Setters
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPhone() {
|
||||||
|
return phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhone(String phone) {
|
||||||
|
this.phone = phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/main/java/com/yundage/chat/dto/ResetPasswordRequest.java
Normal file
31
src/main/java/com/yundage/chat/dto/ResetPasswordRequest.java
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package com.yundage.chat.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
|
||||||
|
public class ResetPasswordRequest {
|
||||||
|
|
||||||
|
@NotBlank(message = "重置令牌不能为空")
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
@NotBlank(message = "新密码不能为空")
|
||||||
|
@Size(min = 6, max = 100, message = "密码长度必须在6-100个字符之间")
|
||||||
|
private String newPassword;
|
||||||
|
|
||||||
|
// Getters and Setters
|
||||||
|
public String getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNewPassword() {
|
||||||
|
return newPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNewPassword(String newPassword) {
|
||||||
|
this.newPassword = newPassword;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package com.yundage.chat.entity;
|
||||||
|
|
||||||
|
import com.mybatisflex.annotation.Id;
|
||||||
|
import com.mybatisflex.annotation.KeyType;
|
||||||
|
import com.mybatisflex.annotation.Table;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Table("password_reset_tokens")
|
||||||
|
public class PasswordResetToken {
|
||||||
|
|
||||||
|
@Id(keyType = KeyType.Auto)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private Long userId;
|
||||||
|
private String token;
|
||||||
|
private LocalDateTime expiresAt;
|
||||||
|
private Boolean used;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
public PasswordResetToken() {
|
||||||
|
this.used = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PasswordResetToken(Long userId, String token, LocalDateTime expiresAt) {
|
||||||
|
this.userId = userId;
|
||||||
|
this.token = token;
|
||||||
|
this.expiresAt = expiresAt;
|
||||||
|
this.used = false;
|
||||||
|
this.createdAt = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters and Setters
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(Long userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getExpiresAt() {
|
||||||
|
return expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpiresAt(LocalDateTime expiresAt) {
|
||||||
|
this.expiresAt = expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getUsed() {
|
||||||
|
return used;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsed(Boolean used) {
|
||||||
|
this.used = used;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(LocalDateTime createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExpired() {
|
||||||
|
return LocalDateTime.now().isAfter(expiresAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValid() {
|
||||||
|
return !used && !isExpired();
|
||||||
|
}
|
||||||
|
}
|
||||||
171
src/main/java/com/yundage/chat/entity/User.java
Normal file
171
src/main/java/com/yundage/chat/entity/User.java
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
package com.yundage.chat.entity;
|
||||||
|
|
||||||
|
import com.mybatisflex.annotation.Id;
|
||||||
|
import com.mybatisflex.annotation.KeyType;
|
||||||
|
import com.mybatisflex.annotation.Table;
|
||||||
|
import com.yundage.chat.enums.UserType;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
@Table("users")
|
||||||
|
public class User implements UserDetails {
|
||||||
|
|
||||||
|
@Id(keyType = KeyType.Auto)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String username;
|
||||||
|
private String passwordHash;
|
||||||
|
private String phone;
|
||||||
|
private String email;
|
||||||
|
private String avatarUrl;
|
||||||
|
private UserType userType;
|
||||||
|
private Integer membershipLevelId;
|
||||||
|
private Integer status;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
private LocalDateTime lastLoginAt;
|
||||||
|
|
||||||
|
public User() {
|
||||||
|
this.userType = UserType.PERSONAL;
|
||||||
|
this.membershipLevelId = 1;
|
||||||
|
this.status = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserDetails implementation
|
||||||
|
@Override
|
||||||
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
|
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + userType.name()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPassword() {
|
||||||
|
return passwordHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsername() {
|
||||||
|
return email != null ? email : phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonExpired() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonLocked() {
|
||||||
|
return status == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCredentialsNonExpired() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return status == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters and Setters
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPasswordHash() {
|
||||||
|
return passwordHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPasswordHash(String passwordHash) {
|
||||||
|
this.passwordHash = passwordHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPhone() {
|
||||||
|
return phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhone(String phone) {
|
||||||
|
this.phone = phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAvatarUrl() {
|
||||||
|
return avatarUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAvatarUrl(String avatarUrl) {
|
||||||
|
this.avatarUrl = avatarUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserType getUserType() {
|
||||||
|
return userType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserType(UserType userType) {
|
||||||
|
this.userType = userType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getMembershipLevelId() {
|
||||||
|
return membershipLevelId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMembershipLevelId(Integer membershipLevelId) {
|
||||||
|
this.membershipLevelId = membershipLevelId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(Integer status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(LocalDateTime createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getUpdatedAt() {
|
||||||
|
return updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdatedAt(LocalDateTime updatedAt) {
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getLastLoginAt() {
|
||||||
|
return lastLoginAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastLoginAt(LocalDateTime lastLoginAt) {
|
||||||
|
this.lastLoginAt = lastLoginAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/main/java/com/yundage/chat/enums/AuthProvider.java
Normal file
20
src/main/java/com/yundage/chat/enums/AuthProvider.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package com.yundage.chat.enums;
|
||||||
|
|
||||||
|
public enum AuthProvider {
|
||||||
|
WECHAT_MINI("wechat_mini"),
|
||||||
|
WECHAT_OPEN("wechat_open"),
|
||||||
|
GOOGLE("google"),
|
||||||
|
GITHUB("github"),
|
||||||
|
APPLE("apple"),
|
||||||
|
CUSTOM("custom");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
AuthProvider(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/main/java/com/yundage/chat/enums/UserType.java
Normal file
17
src/main/java/com/yundage/chat/enums/UserType.java
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package com.yundage.chat.enums;
|
||||||
|
|
||||||
|
public enum UserType {
|
||||||
|
PERSONAL("personal"),
|
||||||
|
ENTERPRISE("enterprise"),
|
||||||
|
ADMIN("admin");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
UserType(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.yundage.chat.mapper;
|
||||||
|
|
||||||
|
import com.yundage.chat.entity.PasswordResetToken;
|
||||||
|
import com.mybatisflex.core.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface PasswordResetTokenMapper extends BaseMapper<PasswordResetToken> {
|
||||||
|
}
|
||||||
9
src/main/java/com/yundage/chat/mapper/UserMapper.java
Normal file
9
src/main/java/com/yundage/chat/mapper/UserMapper.java
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package com.yundage.chat.mapper;
|
||||||
|
|
||||||
|
import com.yundage.chat.entity.User;
|
||||||
|
import com.mybatisflex.core.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface UserMapper extends BaseMapper<User> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.yundage.chat.service;
|
||||||
|
|
||||||
|
import com.yundage.chat.entity.User;
|
||||||
|
import com.yundage.chat.mapper.UserMapper;
|
||||||
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CustomUserDetailsService implements UserDetailsService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserMapper userMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||||
|
.where(User::getEmail).eq(username)
|
||||||
|
.or(User::getPhone).eq(username);
|
||||||
|
|
||||||
|
User user = userMapper.selectOneByQuery(queryWrapper);
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
throw new UsernameNotFoundException("用户不存在: " + username);
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/main/java/com/yundage/chat/service/EmailService.java
Normal file
46
src/main/java/com/yundage/chat/service/EmailService.java
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package com.yundage.chat.service;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.mail.SimpleMailMessage;
|
||||||
|
import org.springframework.mail.javamail.JavaMailSender;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class EmailService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JavaMailSender mailSender;
|
||||||
|
|
||||||
|
@Value("${spring.mail.username}")
|
||||||
|
private String fromEmail;
|
||||||
|
|
||||||
|
@Value("${app.reset-password-url:http://localhost:3000/reset-password}")
|
||||||
|
private String resetPasswordUrl;
|
||||||
|
|
||||||
|
public void sendPasswordResetEmail(String toEmail, String username, String token) {
|
||||||
|
try {
|
||||||
|
SimpleMailMessage message = new SimpleMailMessage();
|
||||||
|
message.setFrom(fromEmail);
|
||||||
|
message.setTo(toEmail);
|
||||||
|
message.setSubject("密码重置 - 云大AI聊天");
|
||||||
|
|
||||||
|
String resetLink = resetPasswordUrl + "?token=" + token;
|
||||||
|
String emailBody = String.format(
|
||||||
|
"亲爱的 %s,\n\n" +
|
||||||
|
"我们收到了您的密码重置请求。请点击以下链接重置您的密码:\n\n" +
|
||||||
|
"%s\n\n" +
|
||||||
|
"此链接将在1小时后过期。如果您没有请求重置密码,请忽略此邮件。\n\n" +
|
||||||
|
"祝好,\n" +
|
||||||
|
"云大AI聊天团队",
|
||||||
|
username, resetLink
|
||||||
|
);
|
||||||
|
|
||||||
|
message.setText(emailBody);
|
||||||
|
mailSender.send(message);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("发送邮件失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
160
src/main/java/com/yundage/chat/service/UserService.java
Normal file
160
src/main/java/com/yundage/chat/service/UserService.java
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
package com.yundage.chat.service;
|
||||||
|
|
||||||
|
import com.yundage.chat.dto.*;
|
||||||
|
import com.yundage.chat.entity.User;
|
||||||
|
import com.yundage.chat.entity.PasswordResetToken;
|
||||||
|
import com.yundage.chat.mapper.UserMapper;
|
||||||
|
import com.yundage.chat.mapper.PasswordResetTokenMapper;
|
||||||
|
import com.yundage.chat.util.JwtUtil;
|
||||||
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UserService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserMapper userMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PasswordResetTokenMapper tokenMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JwtUtil jwtUtil;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AuthenticationManager authenticationManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EmailService emailService;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public AuthResponse register(RegisterRequest request) {
|
||||||
|
// 检查用户是否已存在
|
||||||
|
if (request.getEmail() != null && existsByEmail(request.getEmail())) {
|
||||||
|
throw new RuntimeException("邮箱已被注册");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.getPhone() != null && existsByPhone(request.getPhone())) {
|
||||||
|
throw new RuntimeException("手机号已被注册");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新用户
|
||||||
|
User user = new User();
|
||||||
|
user.setUsername(request.getUsername());
|
||||||
|
user.setEmail(request.getEmail());
|
||||||
|
user.setPhone(request.getPhone());
|
||||||
|
user.setPasswordHash(passwordEncoder.encode(request.getPassword()));
|
||||||
|
user.setCreatedAt(LocalDateTime.now());
|
||||||
|
user.setUpdatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
|
userMapper.insert(user);
|
||||||
|
|
||||||
|
// 生成JWT令牌
|
||||||
|
String token = jwtUtil.generateToken(user.getUsername(), user.getId());
|
||||||
|
|
||||||
|
return new AuthResponse(token, user.getId(), user.getDisplayName(),
|
||||||
|
user.getEmail(), user.getPhone());
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthResponse login(LoginRequest request) {
|
||||||
|
try {
|
||||||
|
Authentication authentication = authenticationManager.authenticate(
|
||||||
|
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
|
||||||
|
);
|
||||||
|
|
||||||
|
User user = (User) authentication.getPrincipal();
|
||||||
|
|
||||||
|
// 更新最后登录时间
|
||||||
|
user.setLastLoginAt(LocalDateTime.now());
|
||||||
|
userMapper.update(user);
|
||||||
|
|
||||||
|
// 生成JWT令牌
|
||||||
|
String token = jwtUtil.generateToken(user.getUsername(), user.getId());
|
||||||
|
|
||||||
|
return new AuthResponse(token, user.getId(), user.getDisplayName(),
|
||||||
|
user.getEmail(), user.getPhone());
|
||||||
|
|
||||||
|
} catch (AuthenticationException e) {
|
||||||
|
throw new RuntimeException("用户名或密码错误");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void requestPasswordReset(PasswordResetRequest request) {
|
||||||
|
User user = findByEmail(request.getEmail());
|
||||||
|
if (user == null) {
|
||||||
|
throw new RuntimeException("用户不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成重置令牌
|
||||||
|
String token = UUID.randomUUID().toString();
|
||||||
|
LocalDateTime expiresAt = LocalDateTime.now().plusHours(1); // 1小时后过期
|
||||||
|
|
||||||
|
PasswordResetToken resetToken = new PasswordResetToken(user.getId(), token, expiresAt);
|
||||||
|
tokenMapper.insert(resetToken);
|
||||||
|
|
||||||
|
// 发送重置邮件
|
||||||
|
emailService.sendPasswordResetEmail(user.getEmail(), user.getDisplayName(), token);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void resetPassword(ResetPasswordRequest request) {
|
||||||
|
// 查找重置令牌
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||||
|
.where(PasswordResetToken::getToken).eq(request.getToken())
|
||||||
|
.and(PasswordResetToken::getUsed).eq(false);
|
||||||
|
|
||||||
|
PasswordResetToken resetToken = tokenMapper.selectOneByQuery(queryWrapper);
|
||||||
|
|
||||||
|
if (resetToken == null || !resetToken.isValid()) {
|
||||||
|
throw new RuntimeException("重置令牌无效或已过期");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新用户密码
|
||||||
|
User user = userMapper.selectOneById(resetToken.getUserId());
|
||||||
|
if (user == null) {
|
||||||
|
throw new RuntimeException("用户不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
user.setPasswordHash(passwordEncoder.encode(request.getNewPassword()));
|
||||||
|
user.setUpdatedAt(LocalDateTime.now());
|
||||||
|
userMapper.update(user);
|
||||||
|
|
||||||
|
// 标记令牌为已使用
|
||||||
|
resetToken.setUsed(true);
|
||||||
|
tokenMapper.update(resetToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public User findByEmail(String email) {
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||||
|
.where(User::getEmail).eq(email);
|
||||||
|
return userMapper.selectOneByQuery(queryWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public User findByPhone(String phone) {
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||||
|
.where(User::getPhone).eq(phone);
|
||||||
|
return userMapper.selectOneByQuery(queryWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean existsByEmail(String email) {
|
||||||
|
return findByEmail(email) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean existsByPhone(String phone) {
|
||||||
|
return findByPhone(phone) != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
91
src/main/java/com/yundage/chat/util/JwtUtil.java
Normal file
91
src/main/java/com/yundage/chat/util/JwtUtil.java
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package com.yundage.chat.util;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.*;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class JwtUtil {
|
||||||
|
|
||||||
|
@Value("${jwt.secret:mySecretKey}")
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
@Value("${jwt.expiration:86400000}") // 24 hours in milliseconds
|
||||||
|
private Long expiration;
|
||||||
|
|
||||||
|
private SecretKey getSigningKey() {
|
||||||
|
return Keys.hmacShaKeyFor(secret.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String generateToken(UserDetails userDetails) {
|
||||||
|
Map<String, Object> claims = new HashMap<>();
|
||||||
|
return createToken(claims, userDetails.getUsername());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String generateToken(String username, Long userId) {
|
||||||
|
Map<String, Object> claims = new HashMap<>();
|
||||||
|
claims.put("userId", userId);
|
||||||
|
return createToken(claims, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String createToken(Map<String, Object> claims, String subject) {
|
||||||
|
return Jwts.builder()
|
||||||
|
.claims(claims)
|
||||||
|
.subject(subject)
|
||||||
|
.issuedAt(new Date(System.currentTimeMillis()))
|
||||||
|
.expiration(new Date(System.currentTimeMillis() + expiration))
|
||||||
|
.signWith(getSigningKey())
|
||||||
|
.compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String extractUsername(String token) {
|
||||||
|
return extractClaim(token, Claims::getSubject);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long extractUserId(String token) {
|
||||||
|
return extractClaim(token, claims -> claims.get("userId", Long.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date extractExpiration(String token) {
|
||||||
|
return extractClaim(token, Claims::getExpiration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
|
||||||
|
final Claims claims = extractAllClaims(token);
|
||||||
|
return claimsResolver.apply(claims);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Claims extractAllClaims(String token) {
|
||||||
|
return Jwts.parser()
|
||||||
|
.verifyWith(getSigningKey())
|
||||||
|
.build()
|
||||||
|
.parseSignedClaims(token)
|
||||||
|
.getPayload();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean isTokenExpired(String token) {
|
||||||
|
return extractExpiration(token).before(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean validateToken(String token, UserDetails userDetails) {
|
||||||
|
final String username = extractUsername(token);
|
||||||
|
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean validateToken(String token) {
|
||||||
|
try {
|
||||||
|
extractAllClaims(token);
|
||||||
|
return !isTokenExpired(token);
|
||||||
|
} catch (JwtException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/main/resources/application.yml
Normal file
52
src/main/resources/application.yml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
url: jdbc:mysql://localhost:3306/chat?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
|
||||||
|
username: root
|
||||||
|
password: password
|
||||||
|
|
||||||
|
# JPA configuration (optional, if you need JPA alongside MyBatis-Flex)
|
||||||
|
jpa:
|
||||||
|
show-sql: true
|
||||||
|
hibernate:
|
||||||
|
ddl-auto: update
|
||||||
|
|
||||||
|
# Mail configuration
|
||||||
|
mail:
|
||||||
|
host: smtp.gmail.com
|
||||||
|
port: 587
|
||||||
|
username: your-email@gmail.com
|
||||||
|
password: your-app-password
|
||||||
|
properties:
|
||||||
|
mail:
|
||||||
|
smtp:
|
||||||
|
auth: true
|
||||||
|
starttls:
|
||||||
|
enable: true
|
||||||
|
|
||||||
|
# MyBatis-Flex configuration
|
||||||
|
mybatis-flex:
|
||||||
|
# Enable SQL logging
|
||||||
|
configuration:
|
||||||
|
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||||
|
# Mapper XML location (optional)
|
||||||
|
mapper-locations: classpath*:mapper/*.xml
|
||||||
|
|
||||||
|
# JWT configuration
|
||||||
|
jwt:
|
||||||
|
secret: mySecretKeyForJWTTokenGenerationAndValidation
|
||||||
|
expiration: 86400000 # 24 hours in milliseconds
|
||||||
|
|
||||||
|
# App configuration
|
||||||
|
app:
|
||||||
|
reset-password-url: http://localhost:3000/reset-password
|
||||||
|
|
||||||
|
# Server configuration
|
||||||
|
server:
|
||||||
|
port: 8080
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
com.yundage.chat: debug
|
||||||
|
com.mybatisflex: debug
|
||||||
113
src/main/resources/schema.sql
Normal file
113
src/main/resources/schema.sql
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
-- Create database
|
||||||
|
CREATE DATABASE IF NOT EXISTS chat CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
USE chat;
|
||||||
|
|
||||||
|
-- 1. 用户主表
|
||||||
|
CREATE TABLE users (
|
||||||
|
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
username VARCHAR(50) DEFAULT NULL COMMENT '昵称/展示名',
|
||||||
|
password_hash VARCHAR(255) DEFAULT NULL COMMENT '哈希密码(可为空)',
|
||||||
|
phone VARCHAR(20) UNIQUE DEFAULT NULL COMMENT '手机号',
|
||||||
|
email VARCHAR(100)UNIQUE DEFAULT NULL COMMENT '邮箱',
|
||||||
|
avatar_url VARCHAR(255) DEFAULT NULL,
|
||||||
|
user_type ENUM('personal','enterprise','admin')
|
||||||
|
NOT NULL DEFAULT 'personal' COMMENT '用户类型',
|
||||||
|
membership_level_id SMALLINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '付费等级',
|
||||||
|
status TINYINT NOT NULL DEFAULT 1 COMMENT '1=正常, 0=封禁',
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
last_login_at DATETIME NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户主表';
|
||||||
|
|
||||||
|
-- 2. 第三方账号绑定表
|
||||||
|
CREATE TABLE user_auth_accounts (
|
||||||
|
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
user_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
provider ENUM('wechat_mini','wechat_open','google','github','apple','custom')
|
||||||
|
NOT NULL COMMENT '登录方式',
|
||||||
|
provider_user_id VARCHAR(100) NOT NULL COMMENT '第三方平台唯一ID',
|
||||||
|
access_token VARCHAR(255) DEFAULT NULL,
|
||||||
|
refresh_token VARCHAR(255) DEFAULT NULL,
|
||||||
|
expires_at DATETIME DEFAULT NULL,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY uq_provider_user (provider, provider_user_id),
|
||||||
|
CONSTRAINT fk_auth_user FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='第三方登录账号绑定表';
|
||||||
|
|
||||||
|
-- 3. 会员等级表
|
||||||
|
CREATE TABLE membership_levels (
|
||||||
|
id SMALLINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
name VARCHAR(50) NOT NULL,
|
||||||
|
price_month DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '月费',
|
||||||
|
daily_msg_limit INT UNSIGNED NOT NULL DEFAULT 20,
|
||||||
|
features JSON DEFAULT NULL COMMENT '权限 JSON',
|
||||||
|
sort_order TINYINT NOT NULL DEFAULT 0,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员等级定义';
|
||||||
|
|
||||||
|
-- 4. 会话表(支持普通与研究模式)
|
||||||
|
CREATE TABLE conversations (
|
||||||
|
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
user_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
title VARCHAR(100) DEFAULT NULL,
|
||||||
|
model_version VARCHAR(50) DEFAULT NULL COMMENT '使用模型版本',
|
||||||
|
chat_mode ENUM('chat','research')
|
||||||
|
NOT NULL DEFAULT 'chat' COMMENT 'chat=普通,research=深度研究',
|
||||||
|
is_active TINYINT NOT NULL DEFAULT 1,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT fk_conv_user FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会话表';
|
||||||
|
|
||||||
|
-- 5. 深度研究扩展表
|
||||||
|
CREATE TABLE conversation_research_meta (
|
||||||
|
conversation_id BIGINT UNSIGNED PRIMARY KEY,
|
||||||
|
topic VARCHAR(200) NOT NULL COMMENT '研究主题',
|
||||||
|
goal TEXT DEFAULT NULL COMMENT '研究目标描述',
|
||||||
|
progress_json JSON DEFAULT NULL COMMENT '阶段进度',
|
||||||
|
draft_report_id BIGINT UNSIGNED DEFAULT NULL COMMENT '草稿报告 ID(预留)',
|
||||||
|
cite_style ENUM('APA','IEEE','GB/T-7714') DEFAULT 'APA',
|
||||||
|
tokens_consumed INT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
last_summary_at DATETIME DEFAULT NULL,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT fk_research_conv FOREIGN KEY (conversation_id)
|
||||||
|
REFERENCES conversations(id) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='深度研究元数据';
|
||||||
|
|
||||||
|
-- 6. 消息表
|
||||||
|
CREATE TABLE messages (
|
||||||
|
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
conversation_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
sequence_no INT UNSIGNED NOT NULL COMMENT '会话内顺序编号',
|
||||||
|
role ENUM('user','assistant','system','tool')
|
||||||
|
NOT NULL,
|
||||||
|
content LONGTEXT NOT NULL,
|
||||||
|
tokens INT UNSIGNED DEFAULT NULL,
|
||||||
|
latency_ms INT UNSIGNED DEFAULT NULL COMMENT '响应耗时',
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT fk_msg_conv FOREIGN KEY (conversation_id) REFERENCES conversations(id)
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
UNIQUE KEY uq_conv_seq (conversation_id, sequence_no)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息表';
|
||||||
|
|
||||||
|
-- 7. 密码重置令牌表(用于密码重置功能)
|
||||||
|
CREATE TABLE password_reset_tokens (
|
||||||
|
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
user_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
token VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
expires_at DATETIME NOT NULL,
|
||||||
|
used TINYINT NOT NULL DEFAULT 0,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT fk_reset_user FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='密码重置令牌表';
|
||||||
|
|
||||||
|
-- 插入默认会员等级数据
|
||||||
|
INSERT INTO membership_levels (id, name, price_month, daily_msg_limit, features, sort_order) VALUES
|
||||||
|
(1, '免费版', 0.00, 20, '{"models": ["gpt-3.5"], "features": ["basic_chat"]}', 1),
|
||||||
|
(2, '专业版', 29.99, 100, '{"models": ["gpt-3.5", "gpt-4"], "features": ["basic_chat", "research_mode"]}', 2),
|
||||||
|
(3, '企业版', 99.99, 1000, '{"models": ["gpt-3.5", "gpt-4", "gpt-4-turbo"], "features": ["basic_chat", "research_mode", "priority_support"]}', 3);
|
||||||
30
target/classes/application.yml
Normal file
30
target/classes/application.yml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
url: jdbc:mysql://localhost:3306/chat?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
|
||||||
|
username: root
|
||||||
|
password: password
|
||||||
|
|
||||||
|
# JPA configuration (optional, if you need JPA alongside MyBatis-Flex)
|
||||||
|
jpa:
|
||||||
|
show-sql: true
|
||||||
|
hibernate:
|
||||||
|
ddl-auto: update
|
||||||
|
|
||||||
|
# MyBatis-Flex configuration
|
||||||
|
mybatis-flex:
|
||||||
|
# Enable SQL logging
|
||||||
|
configuration:
|
||||||
|
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||||
|
# Mapper XML location (optional)
|
||||||
|
mapper-locations: classpath*:mapper/*.xml
|
||||||
|
|
||||||
|
# Server configuration
|
||||||
|
server:
|
||||||
|
port: 8080
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
com.yundage.chat: debug
|
||||||
|
com.mybatisflex: debug
|
||||||
113
target/classes/schema.sql
Normal file
113
target/classes/schema.sql
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
-- Create database
|
||||||
|
CREATE DATABASE IF NOT EXISTS chat CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
USE chat;
|
||||||
|
|
||||||
|
-- 1. 用户主表
|
||||||
|
CREATE TABLE users (
|
||||||
|
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
username VARCHAR(50) DEFAULT NULL COMMENT '昵称/展示名',
|
||||||
|
password_hash VARCHAR(255) DEFAULT NULL COMMENT '哈希密码(可为空)',
|
||||||
|
phone VARCHAR(20) UNIQUE DEFAULT NULL COMMENT '手机号',
|
||||||
|
email VARCHAR(100)UNIQUE DEFAULT NULL COMMENT '邮箱',
|
||||||
|
avatar_url VARCHAR(255) DEFAULT NULL,
|
||||||
|
user_type ENUM('personal','enterprise','admin')
|
||||||
|
NOT NULL DEFAULT 'personal' COMMENT '用户类型',
|
||||||
|
membership_level_id SMALLINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '付费等级',
|
||||||
|
status TINYINT NOT NULL DEFAULT 1 COMMENT '1=正常, 0=封禁',
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
last_login_at DATETIME NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户主表';
|
||||||
|
|
||||||
|
-- 2. 第三方账号绑定表
|
||||||
|
CREATE TABLE user_auth_accounts (
|
||||||
|
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
user_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
provider ENUM('wechat_mini','wechat_open','google','github','apple','custom')
|
||||||
|
NOT NULL COMMENT '登录方式',
|
||||||
|
provider_user_id VARCHAR(100) NOT NULL COMMENT '第三方平台唯一ID',
|
||||||
|
access_token VARCHAR(255) DEFAULT NULL,
|
||||||
|
refresh_token VARCHAR(255) DEFAULT NULL,
|
||||||
|
expires_at DATETIME DEFAULT NULL,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY uq_provider_user (provider, provider_user_id),
|
||||||
|
CONSTRAINT fk_auth_user FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='第三方登录账号绑定表';
|
||||||
|
|
||||||
|
-- 3. 会员等级表
|
||||||
|
CREATE TABLE membership_levels (
|
||||||
|
id SMALLINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
name VARCHAR(50) NOT NULL,
|
||||||
|
price_month DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '月费',
|
||||||
|
daily_msg_limit INT UNSIGNED NOT NULL DEFAULT 20,
|
||||||
|
features JSON DEFAULT NULL COMMENT '权限 JSON',
|
||||||
|
sort_order TINYINT NOT NULL DEFAULT 0,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员等级定义';
|
||||||
|
|
||||||
|
-- 4. 会话表(支持普通与研究模式)
|
||||||
|
CREATE TABLE conversations (
|
||||||
|
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
user_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
title VARCHAR(100) DEFAULT NULL,
|
||||||
|
model_version VARCHAR(50) DEFAULT NULL COMMENT '使用模型版本',
|
||||||
|
chat_mode ENUM('chat','research')
|
||||||
|
NOT NULL DEFAULT 'chat' COMMENT 'chat=普通,research=深度研究',
|
||||||
|
is_active TINYINT NOT NULL DEFAULT 1,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT fk_conv_user FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会话表';
|
||||||
|
|
||||||
|
-- 5. 深度研究扩展表
|
||||||
|
CREATE TABLE conversation_research_meta (
|
||||||
|
conversation_id BIGINT UNSIGNED PRIMARY KEY,
|
||||||
|
topic VARCHAR(200) NOT NULL COMMENT '研究主题',
|
||||||
|
goal TEXT DEFAULT NULL COMMENT '研究目标描述',
|
||||||
|
progress_json JSON DEFAULT NULL COMMENT '阶段进度',
|
||||||
|
draft_report_id BIGINT UNSIGNED DEFAULT NULL COMMENT '草稿报告 ID(预留)',
|
||||||
|
cite_style ENUM('APA','IEEE','GB/T-7714') DEFAULT 'APA',
|
||||||
|
tokens_consumed INT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
last_summary_at DATETIME DEFAULT NULL,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT fk_research_conv FOREIGN KEY (conversation_id)
|
||||||
|
REFERENCES conversations(id) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='深度研究元数据';
|
||||||
|
|
||||||
|
-- 6. 消息表
|
||||||
|
CREATE TABLE messages (
|
||||||
|
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
conversation_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
sequence_no INT UNSIGNED NOT NULL COMMENT '会话内顺序编号',
|
||||||
|
role ENUM('user','assistant','system','tool')
|
||||||
|
NOT NULL,
|
||||||
|
content LONGTEXT NOT NULL,
|
||||||
|
tokens INT UNSIGNED DEFAULT NULL,
|
||||||
|
latency_ms INT UNSIGNED DEFAULT NULL COMMENT '响应耗时',
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT fk_msg_conv FOREIGN KEY (conversation_id) REFERENCES conversations(id)
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
UNIQUE KEY uq_conv_seq (conversation_id, sequence_no)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息表';
|
||||||
|
|
||||||
|
-- 7. 密码重置令牌表(用于密码重置功能)
|
||||||
|
CREATE TABLE password_reset_tokens (
|
||||||
|
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
user_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
token VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
expires_at DATETIME NOT NULL,
|
||||||
|
used TINYINT NOT NULL DEFAULT 0,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT fk_reset_user FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='密码重置令牌表';
|
||||||
|
|
||||||
|
-- 插入默认会员等级数据
|
||||||
|
INSERT INTO membership_levels (id, name, price_month, daily_msg_limit, features, sort_order) VALUES
|
||||||
|
(1, '免费版', 0.00, 20, '{"models": ["gpt-3.5"], "features": ["basic_chat"]}', 1),
|
||||||
|
(2, '专业版', 29.99, 100, '{"models": ["gpt-3.5", "gpt-4"], "features": ["basic_chat", "research_mode"]}', 2),
|
||||||
|
(3, '企业版', 99.99, 1000, '{"models": ["gpt-3.5", "gpt-4", "gpt-4-turbo"], "features": ["basic_chat", "research_mode", "priority_support"]}', 3);
|
||||||
Reference in New Issue
Block a user