Welcome to contribute to the Websoft9 project! This guide will help you understand how to contribute to the project.
- Project Overview
- Development Environment Setup
- Development Standards
- Contribution Process
- Code Review
- Testing Standards
- Security Standards
- Community Participation
Websoft9 is a modern cloud application management solution platform with layered architecture design, providing full lifecycle services for application deployment, monitoring, and management.
- API Service: Backend service based on Golang + Gin + GORM
- Websoft9 Agent: Client agent deployed on server nodes
- Web UI: Vue 3 + Element Plus frontend interface (planned)
- Backend: Go 1.24+, Gin, GORM, SQLite/MySQL, Redis, InfluxDB
- Frontend: Vue 3, TypeScript, Element Plus, Pinia
- Infrastructure: Docker, GitHub Actions
- Go 1.24+
- Node.js 18+
- Docker 20.10+
- Git 2.30+
- Linux
-
Fork and Clone Repository
git clone https://github.com/Websoft9/webox.git cd webox -
Setup Development Environment
# Install Go dependencies cd api-service go mod tidy # Start API service make run
-
Start Agent (Optional)
cd websoft9-agent go mod tidy make build sudo ./websoft9-agent
- IDE: VS Code, GoLand
- API Testing: Apifox, Postman
- Database: DBeaver, TablePlus
- Container: Docker Desktop
- Follow Go Code Review Comments
- Use
gofmtandgoimportsto format code - Use
golangci-lintfor code checking - Use
gosecfor security checks
// Correct function comments and naming
// CreateUser creates a new user with the given information.
// It returns the created user or an error if the operation fails.
func (s *UserService) CreateUser(ctx context.Context, req *CreateUserRequest) (*User, error) {
// Validate input parameters
if err := s.validateCreateUserRequest(req); err != nil {
return nil, errors.Wrap(err, "invalid create user request")
}
// Create user
user, err := s.repo.Create(ctx, req)
if err != nil {
return nil, errors.Wrap(err, "failed to create user in database")
}
return user, nil
}api-service/
├── cmd/server/ # Application entry point
├── internal/ # Private application code
│ ├── controller/ # API controllers
│ ├── service/ # Business logic layer
│ ├── repository/ # Data access layer
│ ├── model/ # Data models
│ ├── middleware/ # Middleware
│ └── config/ # Configuration management
├── pkg/ # Public library code
├── api/ # API documentation
├── configs/ # Configuration files
└── docs/ # Project documentation
- Package names: Lowercase, short, meaningful nouns
- Variable names: CamelCase, starting with lowercase
- Constant names: All uppercase, underscore separated
- Function names: CamelCase, public functions start with uppercase
- Structs: CamelCase, starting with uppercase
- Use standard HTTP methods: GET, POST, PUT, DELETE, PATCH
- URL design follows RESTful principles
- Use appropriate HTTP status codes
// Route definition example
func SetupRoutes(r *gin.Engine) {
api := r.Group("/api/v1")
{
users := api.Group("/users")
{
users.GET("", userHandler.ListUsers) // GET /api/v1/users
users.POST("", userHandler.CreateUser) // POST /api/v1/users
users.GET("/:id", userHandler.GetUser) // GET /api/v1/users/:id
users.PUT("/:id", userHandler.UpdateUser) // PUT /api/v1/users/:id
users.DELETE("/:id", userHandler.DeleteUser) // DELETE /api/v1/users/:id
}
}
}type APIResponse struct {
Success bool `json:"success"`
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
Error *APIError `json:"error,omitempty"`
}
type PaginatedResponse struct {
Items interface{} `json:"items"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
TotalPages int `json:"total_pages"`
}- Use standard error interface
- Error messages should be clear and specific
- Use
api-service/pkg/errorsto add context information
import "api-service/pkg/errors"
// Demo: GetByID retrieves a permission by ID
func (r *permissionRepository) GetByID(ctx context.Context, id uint) (*model.Permission, error) {
var permission model.Permission
err := r.db.WithContext(ctx).Where("status != -1").First(&permission, id).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.NewAppError(errors.CodeRecordNotFound)
}
return nil, errors.NewAppErrorWrapError(err, errors.CodeRecordQueryFailed)
}
return &permission, nil
}- Use
api-service/pkg/errors/codesto define error codes, internationalization handling (i18n message keys), and HTTP status codes
// Error code constants definition based on API documentation
// Error codes are organized by category with specific ranges for easy identification
const (
// System related error codes (6000-6999)
CodeInternalError ErrorCode = 6001 // System Internal Error
)
// CodeToI18nKey maps error codes to their i18n message keys
// These keys should correspond to entries in the i18n locale files
var CodeToI18nKey = map[ErrorCode]string{
// System related errors (6000-6999)
CodeInternalError: "system.internal_error",
}
// codeToHTTPStatus maps business error codes to HTTP status codes
// This mapping ensures consistent HTTP responses for different error types
var CodeToHTTPStatus = map[ErrorCode]HTTPCode{
// System related errors (6000-6999)
CodeInternalError: http.StatusInternalServerError,
}- Use
api-service/pkg/errors/errorsto create error objects
// Predefined common errors with internationalization support
// These errors can be reused throughout the application for consistency
var (
// System related errors (6000-6999)
ErrInternalError = NewAppErrorWithI18n(CodeInternalError, CodeToI18nKey[CodeInternalError])
)- Follow the
error handling specificationsfor exception error handling:
// Example 1: Create an error message and include the original error
err := errors.NewAppErrorWrapError(err, errors.ErrInternalError)
return err
// Example 2: Create an error message
err := errors.NewAppError(errors.CodeRecordNotFound)
return err
// Example 3: Use error message directly
return errors.ErrInternalError- Use
api-service/configs/lang/<LANGUAGE>.yamlto define multilingual translations and unified naming - Use
pkg/i18nfor multilingual support
// Use pkg/i18n for multilingual support
import (
"api-service/pkg/errors"
"api-service/pkg/logger"
)
func (s *userService) CreateUser(ctx *gin.Context) error {
var req request.CreateUserRequest
if req.Email == "" {
logger.ErrorContext(ctx.Request.Context(), "Email cannot be empty", logger.ErrorField(err))
// Use custom multilingual translation
return errors.NewAppErrorWithI18n(errors.CodeInvalidEmailFormat, "user.email_required")
// Use predefined multilingual translation from error definition
// return errors.NewAppError(errors.CodeInvalidEmailFormat)
}
// Business logic
}- Use structured logging (recommend zap, it's implemented by default.)
- Log levels: DEBUG, INFO, WARN, ERROR, FATAL
- Include necessary context information
import "api-service/pkg/logger"
func (s *UserService) CreateUser(ctx context.Context, req *CreateUserRequest) (*User, error) {
s.logger.InfoContext(ctx, "Creating user", logger.String("username", req.Username))
logger.Info("Creating new user")
user, err := s.repo.Create(req)
if err != nil {
s.logger.ErrorContext(ctx, "Failed to create user", logger.ErrorField(err))
return nil, errors.WrapError(err, errors.CodeInternalError, "Failed to create user")
}
s.logger.InfoContext(ctx, "User created successfully", logger.Uint("user_id", user.ID))
return s.buildUserResponse(user), nil
}We adopt the Git Flow workflow model:
main (production branch)
├── develop (development branch)
├── release/v1.2.0 (release branch)
├── feature/user-authentication (feature branch)
└── hotfix/critical-bug-fix (hotfix branch)
| Branch Type | Naming Format | Example | Purpose |
|---|---|---|---|
| Main branch | main |
main |
Production environment code |
| Development branch | develop |
develop |
Development environment code |
| Feature branch | feature/feature-description |
feature/user-management |
New feature development |
| Release branch | release/version-number |
release/v1.2.0 |
Release preparation |
| Hotfix branch | hotfix/issue-description |
hotfix/login-error |
Emergency fixes |
| Bugfix branch | bugfix/issue-description |
bugfix/api-validation |
General fixes |
Adopt Conventional Commits specification:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
| Type | Description | Example |
|---|---|---|
feat |
New feature | feat(auth): add JWT token refresh mechanism |
fix |
Bug fix | fix(api): handle null pointer in user service |
docs |
Documentation update | docs(readme): update installation instructions |
style |
Code formatting | style(user): format code with gofmt |
refactor |
Code refactoring | refactor(db): extract connection logic to separate package |
test |
Test related | test(user): add unit tests for user service |
chore |
Build process or auxiliary tool changes | chore(deps): update golang to 1.24 |
perf |
Performance optimization | perf(api): optimize database queries |
ci |
CI/CD related | ci(github): add automated testing workflow |
feat(auth): add JWT token refresh mechanism
- Implement automatic token refresh
- Add refresh token storage
- Handle token expiration gracefully
Closes #123-
Create Feature Branch
git checkout develop git pull origin develop git checkout -b feature/user-management
-
Develop Feature
# Write code # Add tests # Update documentation git add . git commit -m "feat(user): add user creation functionality"
-
Push Branch and Create PR
git push origin feature/user-management # Create Pull Request on GitHub -
Code Review and Merge
- Wait for code review
- Modify code based on feedback
- Merge to develop branch after review approval
-
Clean Up Branch
git checkout develop git pull origin develop git branch -d feature/user-management
<type>[scope]: <description>
Examples:
feat(auth): add OAuth2 integrationfix(api): resolve memory leak in user servicedocs(readme): update development setup guide
## Change Type
- [ ] New feature (feature)
- [ ] Bug fix (fix)
- [ ] Documentation update (docs)
- [ ] Code refactoring (refactor)
- [ ] Performance optimization (perf)
- [ ] Test related (test)
- [ ] Other (chore)
## Change Description
Brief description of the changes and purpose.
## Related Issues
Closes #123
Fixes #456
## Testing Instructions
- [ ] Added unit tests
- [ ] Added integration tests
- [ ] Performed manual testing
- [ ] Test coverage ≥ 80%
## Checklist
- [ ] Code follows project coding standards
- [ ] Updated relevant documentation
- [ ] Passed all automated tests
- [ ] Performed code self-review
- [ ] No obvious performance issues
## Screenshots/Demo
If UI changes are involved, please provide screenshots or demo videos.
## Additional Notes
Any other information that needs to be explained.- Is the functionality correctly implemented according to requirements
- Are boundary conditions properly handled
- Is error handling comprehensive
- Does performance meet requirements
- Does code follow project standards
- Is there duplicate code
- Are variable and function names clear
- Are comments sufficient and accurate
- Does it follow SOLID principles
- Are there security vulnerabilities
- Is sensitive information properly handled
- Is input validation sufficient
- Is permission control correct
- Is there sufficient test coverage
- Are test cases reasonable
- Are error scenarios tested
Use the following tags for feedback:
MUST: Issues that must be fixedSHOULD: Issues that should be fixedCOULD: Optional improvement suggestionsQUESTION: Questions that need clarificationPRAISE: Code worth praising
Example:
MUST: There's a risk of null pointer exception here, need to add nil check.
SHOULD: Suggest extracting this magic number as a constant to improve code readability.
COULD: Consider using a more concise approach: `return err != nil`
QUESTION: What's the time complexity of this function? Does it need optimization?
PRAISE: This error handling is well written, providing clear context information.
Adopt the test pyramid model:
/\
/ \ E2E Tests (10%)
/____\
/ \
/ \ Integration Tests (20%)
\________/
\ /
\______/ Unit Tests (70%)
// user_service_test.go
package user
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func TestUserService_CreateUser(t *testing.T) {
tests := []struct {
name string
request *CreateUserRequest
setup func(*MockUserRepository)
want *User
wantErr bool
}{
{
name: "successful user creation",
request: &CreateUserRequest{
Username: "testuser",
Email: "test@example.com",
Password: "password123",
},
setup: func(repo *MockUserRepository) {
repo.On("Create", mock.Anything, mock.Anything).
Return(&User{ID: 1, Username: "testuser"}, nil)
},
want: &User{ID: 1, Username: "testuser"},
wantErr: false,
},
{
name: "invalid email format",
request: &CreateUserRequest{
Username: "testuser",
Email: "invalid-email",
Password: "password123",
},
setup: func(repo *MockUserRepository) {},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
repo := &MockUserRepository{}
tt.setup(repo)
service := NewUserService(repo)
got, err := service.CreateUser(context.Background(), tt.request)
if tt.wantErr {
assert.Error(t, err)
assert.Nil(t, got)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
}
repo.AssertExpectations(t)
})
}
}- Unit test coverage ≥ 80%
- Integration test coverage ≥ 60%
- Critical business logic coverage ≥ 90%
# Run all tests
make test
# Run tests and generate coverage report
make test-coverage
# Run tests for specific package
go test -v ./internal/service/...
# Run integration tests
make test-integration❌ Wrong Example - Hardcoded Sensitive Information
const (
DBPassword = "password123"
APIKey = "sk-1234567890abcdef"
JWTSecret = "my-secret-key"
)✅ Correct Example - Using Environment Variables
type Config struct {
DBPassword string `env:"DB_PASSWORD,required"`
APIKey string `env:"API_KEY,required"`
JWTSecret string `env:"JWT_SECRET,required"`
}
func LoadConfig() (*Config, error) {
var config Config
if err := env.Parse(&config); err != nil {
return nil, errors.Wrap(err, "failed to parse config")
}
return &config, nil
}import (
"github.com/go-playground/validator/v10"
"html"
"strings"
)
var validate = validator.New()
type CreateUserRequest struct {
Username string `json:"username" validate:"required,min=3,max=50,alphanum"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=8"`
}
func (r *CreateUserRequest) Validate() error {
if err := validate.Struct(r); err != nil {
return errors.Wrap(err, "validation failed")
}
// Custom validation
if err := ValidatePasswordStrength(r.Password); err != nil {
return err
}
return nil
}
func (r *CreateUserRequest) Sanitize() {
r.Username = strings.TrimSpace(r.Username)
r.Email = strings.ToLower(strings.TrimSpace(r.Email))
// HTML escape to prevent XSS
r.Username = html.EscapeString(r.Username)
}// ❌ Wrong Example - Vulnerable to SQL injection
func GetUserByUsername(username string) (*User, error) {
query := fmt.Sprintf("SELECT * FROM users WHERE username = '%s'", username)
rows, err := db.Query(query)
// ...
}
// ✅ Correct Example - Using parameterized queries
func GetUserByUsername(username string) (*User, error) {
query := "SELECT * FROM users WHERE username = ?"
rows, err := db.Query(query, username)
// ...
}
// ✅ Using GORM (Recommended)
func (r *userRepository) GetByUsername(ctx context.Context, username string) (*User, error) {
var user User
err := r.db.WithContext(ctx).Where("username = ?", username).First(&user).Error
if err != nil {
return nil, errors.Wrap(err, "failed to get user by username")
}
return &user, nil
}- No hardcoded passwords, keys, or sensitive information
- All user inputs are validated and sanitized
- Use parameterized queries to prevent SQL injection
- Implement appropriate authentication and authorization mechanisms
- Sensitive data is encrypted during transmission and storage
- Implement appropriate error handling without leaking sensitive information
- Use secure random number generators
- Implement appropriate logging and auditing
If you find bugs or have feature suggestions, please:
- Search existing Issues to avoid duplicate reports
- Use appropriate Issue templates
- Provide detailed reproduction steps and environment information
- For security issues, please contact maintainers privately
- Describe your needs in Issues
- Explain why this feature is useful
- Provide specific use cases
- Consider backward compatibility
- Fix errors in documentation
- Improve clarity of existing documentation
- Add missing documentation
- Translate documentation to other languages
We are committed to providing a friendly, safe, and welcoming environment for everyone. Please:
- Use friendly and inclusive language
- Respect different viewpoints and experiences
- Gracefully accept constructive criticism
- Focus on what is best for the community
- Show empathy towards other community members
- GitHub Issues: Report bugs and feature requests
- GitHub Discussions: General discussions and questions
Thanks to all developers who have contributed to the Websoft9 project! Your contributions make this project better.
Happy Coding! 🚀