From ddec42281385a7da9fac4ff30fcabfe485561cef Mon Sep 17 00:00:00 2001 From: nannanwu Date: Tue, 24 Feb 2026 20:44:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E4=BC=98=E5=8C=96=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E5=A4=84=E7=90=86=E5=92=8C=E9=85=8D=E7=BD=AE=E7=AB=AF?= =?UTF-8?q?=E5=8F=A3=E5=B9=B6=E6=B7=BB=E5=8A=A0=E9=A1=B9=E7=9B=AE=E8=A7=84?= =?UTF-8?q?=E8=8C=83=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将所有文件路径转换为 URL 友好格式,统一使用正斜杠,确保跨平台兼容 - 修改默认服务器监听端口为 :8989,提升默认配置适用性 - 数据库初始化时创建数据库文件夹,避免路径不存在导致错误 - 新增 AGENTS.md 文档,详细规范项目开发流程、代码风格、架构设计和安全性能指导 - 修正头像路径显示逻辑,确保头像 URL 正确展示 - 增加应用启动和开发的标准操作指南,提升团队协作效率 --- AGENTS.md | 285 +++++++++++++++++++++++++++++ internal/config/config.go | 2 +- internal/controllers/discussion.go | 2 + internal/controllers/home.go | 13 +- internal/controllers/user.go | 8 +- internal/repositories/db.go | 9 +- internal/services/message.go | 6 +- 7 files changed, 319 insertions(+), 6 deletions(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..8e8fdac --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,285 @@ +# AGENTS.md - lv8girl Project Guidelines + +This document provides guidelines for AI agents working on the lv8girl project - a Go-based forum system. + +## Project Overview + +**lv8girl** is a Go + Gin + SQLite forum system with user registration, posts, comments, likes, private messages, and admin dashboard. + +## Build & Development Commands + +### Basic Commands +```bash +# Install dependencies +go mod tidy + +# Run the application +go run ./cmd/server + +# Build binary +go build -o lv8girl ./cmd/server + +# Run tests (if any) +go test ./... + +# Run specific test +go test ./internal/services -v + +# Test coverage +go test ./... -cover +``` + +### Database Operations +```bash +# Database is automatically created at: data/lv8girl.db +# Auto-migration runs on application start +``` + +### Code Quality +```bash +# Format code +go fmt ./... + +# Vet code for suspicious constructs +go vet ./... + +# Static analysis +go install honnef.co/go/tools/cmd/staticcheck@latest +staticcheck ./... +``` + +## Code Style Guidelines + +### General Principles +- Follow **Go standard conventions** and **effective Go guidelines** +- Use **layered architecture**: Controllers → Services → Repositories → Models +- Keep functions small and focused (single responsibility) +- Prefer composition over inheritance +- Handle errors properly - never ignore them + +### Imports Organization +```go +import ( + // Standard library + "fmt" + "time" + "net/http" + + // Third-party packages + "github.com/gin-gonic/gin" + "gorm.io/gorm" + + // Local packages (project-specific) + "lv8girl/internal/models" + "lv8girl/internal/services" +) +``` + +### Naming Conventions +- **Packages**: lowercase, single word, descriptive (e.g., `controllers`, `services`) +- **Interfaces**: `-er` suffix (e.g., `Repository`, `Handler`) +- **Variables**: camelCase, descriptive names +- **Constants**: PascalCase or ALL_CAPS for exported constants +- **Methods**: camelCase, verbs for actions (e.g., `GetUserByID`, `CreatePost`) + +### Error Handling +```go +// Always check errors +if err != nil { + return fmt.Errorf("context: %w", err) +} + +// Use custom error types for domain errors +type ValidationError struct { + Field string + Message string +} + +func (e *ValidationError) Error() string { + return fmt.Sprintf("%s: %s", e.Field, e.Message) +} +``` + +### Types and Structs +```go +// Use JSON tags for API responses +type User struct { + ID uint `json:"id"` + Username string `json:"username"` + Email string `json:"email"` + CreatedAt time.Time `json:"created_at"` +} + +// Use GORM tags for database models +type Discussion struct { + ID uint `gorm:"primaryKey" json:"id"` + Title string `gorm:"size:200;not null" json:"title"` + Content string `gorm:"type:text;not null" json:"content"` +} +``` + +### Function Signatures +```go +// Return errors as last parameter +func GetUserByID(id uint) (*models.User, error) + +// Use context for cancellation and timeouts +func GetUser(ctx context.Context, id uint) (*models.User, error) + +// Receiver names should be short (1-2 letters) +func (c *Controller) HandleRequest(ctx *gin.Context) +``` + +### Project Structure Patterns +``` +cmd/server/main.go # Application entry point +internal/config/ # Configuration management +internal/controllers/ # HTTP request handlers (Gin) +internal/services/ # Business logic layer +internal/repositories/ # Data access layer (GORM) +internal/models/ # Data models +internal/middleware/ # HTTP middleware +internal/routes/ # Route definitions +internal/utils/ # Utility functions +``` + +### Controller Patterns +```go +// Use dependency injection +type AuthController struct { + authService *services.AuthService +} + +func NewAuthController() *AuthController { + return &AuthController{ + authService: services.NewAuthService(), + } +} + +// Handle HTTP requests with Gin +func (c *AuthController) Login(ctx *gin.Context) { + // Parse input + // Call service + // Return response +} +``` + +### Service Layer Patterns +```go +// Services handle business logic +type AuthService struct { + userRepo *repositories.UserRepository +} + +func (s *AuthService) Login(login, password string) LoginResult { + // Business logic + // Call repositories + // Return result +} +``` + +### Repository Patterns +```go +// Repositories handle database operations +type UserRepository struct { + db *gorm.DB +} + +func (r *UserRepository) FindByID(id uint) (*models.User, error) { + var user models.User + result := r.db.First(&user, id) + return &user, result.Error +} +``` + +### Testing Guidelines +```go +// When adding tests, follow these patterns: +func TestUserService_CreateUser(t *testing.T) { + // Arrange + // Act + // Assert +} + +// Use table-driven tests +func TestValidateUser(t *testing.T) { + tests := []struct { + name string + user models.User + expected error + }{ + // test cases + } +} +``` + +### Security Considerations +- Never commit secrets (session keys, passwords) +- Use environment variables for sensitive configuration +- Validate all user input +- Use prepared statements (GORM handles this) +- Hash passwords with bcrypt/scrypt + +### Performance Guidelines +- Use connection pooling (GORM handles this) +- Implement pagination for lists +- Cache frequently accessed data +- Use indexes on frequently queried columns + +### Documentation +- Document exported functions and types +- Use clear, concise comments for complex logic +- Update README.md for new features +- Keep API documentation in sync with code + +## Common Tasks + +### Adding a New Feature +1. Add model in `internal/models/` +2. Add repository methods in `internal/repositories/` +3. Add service logic in `internal/services/` +4. Add controller in `internal/controllers/` +5. Add routes in `internal/routes/routes.go` +6. Add templates in `templates/` (if needed) + +### Database Migrations +- Models are auto-migrated on startup +- For manual migrations, create migration files +- Test migrations before applying to production + +### API Endpoints +- Use RESTful conventions where appropriate +- Return appropriate HTTP status codes +- Use JSON for API responses +- Document API changes + +## Development Workflow + +1. **Before starting**: Run `go mod tidy` to ensure dependencies are current +2. **During development**: Use `go run ./cmd/server` for testing +3. **Before committing**: Run `go fmt ./...` and `go vet ./...` +4. **Testing**: Write tests for new functionality +5. **Documentation**: Update relevant documentation + +## Project-Specific Notes + +- Default admin credentials: `admin` / `admin123` +- Session secret is hardcoded in config - change for production +- Database path: `data/lv8girl.db` +- Server runs on port `:8989` by default +- Uses Gin web framework with HTML templates +- Uses GORM with SQLite (no CGO required) + +## Troubleshooting + +- **Database issues**: Check `data/` directory permissions +- **Import errors**: Run `go mod tidy` +- **Build errors**: Ensure Go 1.21+ is installed +- **Runtime errors**: Check logs for detailed error messages + +## Resources + +- [Go Documentation](https://golang.org/doc/) +- [Gin Framework](https://gin-gonic.com/docs/) +- [GORM Documentation](https://gorm.io/docs/) +- [Effective Go](https://golang.org/doc/effective_go) diff --git a/internal/config/config.go b/internal/config/config.go index 09c909f..fec9678 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -31,7 +31,7 @@ type AppConfig struct { var AppConfigInstance = &Config{ Server: ServerConfig{ - Port: ":8080", + Port: ":8989", ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, }, diff --git a/internal/controllers/discussion.go b/internal/controllers/discussion.go index a08af92..6998b76 100644 --- a/internal/controllers/discussion.go +++ b/internal/controllers/discussion.go @@ -87,6 +87,8 @@ func (c *DiscussionController) CreatePost(ctx *gin.Context) { }) return } + // 将路径转换为 URL 友好的格式 + imagePath = filepath.ToSlash(imagePath) } if err := c.discussionSvc.CreatePost(userID, title, content, imagePath); err != nil { diff --git a/internal/controllers/home.go b/internal/controllers/home.go index 99a243c..89e1555 100644 --- a/internal/controllers/home.go +++ b/internal/controllers/home.go @@ -2,6 +2,7 @@ package controllers import ( "net/http" + "path/filepath" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" @@ -40,6 +41,14 @@ func (c *HomeController) Index(ctx *gin.Context) { posts, _ := c.discussionSvc.GetApprovedPosts(30) + // 处理头像路径,确保使用正斜杠并添加前导斜杠 + for i := range posts { + if posts[i].Avatar != "" { + avatarPath := filepath.ToSlash(posts[i].Avatar) + posts[i].Avatar = "/" + avatarPath + } + } + var postCount, userCount, onlineCount int64 postCount, _ = c.discussionRepo.CountByStatus("approved") userCount, onlineCount, _ = c.userSvc.GetUserStats() @@ -93,7 +102,9 @@ func (c *HomeController) ShowPost(ctx *gin.Context) { avatar := "" if detail.Post.User.Avatar != "" { - avatar = "/" + detail.Post.User.Avatar + // 确保路径使用正斜杠,并添加前导斜杠 + avatarPath := filepath.ToSlash(detail.Post.User.Avatar) + avatar = "/" + avatarPath } ctx.HTML(http.StatusOK, "post.html", gin.H{ diff --git a/internal/controllers/user.go b/internal/controllers/user.go index 21b480c..18f1600 100644 --- a/internal/controllers/user.go +++ b/internal/controllers/user.go @@ -47,7 +47,9 @@ func (c *UserController) ShowProfile(ctx *gin.Context) { avatar := "" if profile.User.Avatar != "" { - avatar = "/" + profile.User.Avatar + // 确保路径使用正斜杠,并添加前导斜杠 + avatarPath := filepath.ToSlash(profile.User.Avatar) + avatar = "/" + avatarPath } ctx.HTML(http.StatusOK, "profile.html", gin.H{ @@ -93,6 +95,8 @@ func (c *UserController) UploadAvatar(ctx *gin.Context) { return } - c.userSvc.UpdateAvatar(userID, imagePath) + // 将路径转换为 URL 友好的格式(使用正斜杠) + avatarPath := filepath.ToSlash(imagePath) + c.userSvc.UpdateAvatar(userID, avatarPath) ctx.Redirect(http.StatusFound, "/profile?success=头像更新成功") } diff --git a/internal/repositories/db.go b/internal/repositories/db.go index d6f9411..565a537 100644 --- a/internal/repositories/db.go +++ b/internal/repositories/db.go @@ -1,7 +1,8 @@ package repositories import ( - "time" + "os" + "path/filepath" "lv8girl/internal/models" @@ -13,6 +14,12 @@ import ( var DB *gorm.DB func Init(databasePath string) error { + // 确保数据库文件所在目录存在 + dir := filepath.Dir(databasePath) + if err := os.MkdirAll(dir, 0755); err != nil { + return err + } + var err error DB, err = gorm.Open(sqlite.Open(databasePath), &gorm.Config{ Logger: logger.Default.LogMode(logger.Info), diff --git a/internal/services/message.go b/internal/services/message.go index 68281bd..8ad77f4 100644 --- a/internal/services/message.go +++ b/internal/services/message.go @@ -1,6 +1,8 @@ package services import ( + "path/filepath" + "lv8girl/internal/models" "lv8girl/internal/repositories" ) @@ -68,7 +70,9 @@ func (s *MessageService) GetConversations(userID uint) ([]ConversationView, erro avatar := "" if otherUser.Avatar != "" { - avatar = "/" + otherUser.Avatar + // 确保路径使用正斜杠,并添加前导斜杠 + avatarPath := filepath.ToSlash(otherUser.Avatar) + avatar = "/" + avatarPath } conversations = append(conversations, ConversationView{