Compare commits
2 Commits
c29cbdf847
...
51025195a5
| Author | SHA1 | Date | |
|---|---|---|---|
|
51025195a5
|
|||
|
d1e6f15745
|
30
.env.example
30
.env.example
@@ -1,20 +1,22 @@
|
||||
# Server Configuration
|
||||
PORT=3000
|
||||
ENVIRONMENT=development
|
||||
# 使用 APP_ 前缀,优先级高于 config.yaml
|
||||
APP_SERVER_PORT=3000
|
||||
APP_SERVER_ENVIRONMENT=development
|
||||
|
||||
# Database Configuration
|
||||
# Possible values for DATABASE_DRIVER: sqlite, postgres
|
||||
DATABASE_DRIVER=sqlite
|
||||
DATABASE_PATH=./data/database.sqlite
|
||||
# 使用 APP_ 前缀
|
||||
# Possible values for APP_DATABASE_DRIVER: sqlite, postgres
|
||||
APP_DATABASE_DRIVER=sqlite
|
||||
APP_DATABASE_SQLITE_PATH=./data/database.sqlite
|
||||
|
||||
# PostgreSQL Configuration (Only if DATABASE_DRIVER is postgres)
|
||||
POSTGRES_HOST=localhost
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_USER=trace
|
||||
POSTGRES_PASSWORD=trace123
|
||||
POSTGRES_DB=trace
|
||||
POSTGRES_SSLMODE=disable
|
||||
# PostgreSQL Configuration (Only if APP_DATABASE_DRIVER is postgres)
|
||||
APP_DATABASE_POSTGRES_HOST=localhost
|
||||
APP_DATABASE_POSTGRES_PORT=5432
|
||||
APP_DATABASE_POSTGRES_USER=trace
|
||||
APP_DATABASE_POSTGRES_PASSWORD=trace123
|
||||
APP_DATABASE_POSTGRES_DBNAME=trace
|
||||
APP_DATABASE_POSTGRES_SSLMODE=disable
|
||||
|
||||
# JWT Configuration
|
||||
JWT_SECRET=your-secret-key-here-change-in-production
|
||||
JWT_EXPIRE=7200
|
||||
APP_JWT_SECRET=your-secret-key-here-change-in-production
|
||||
APP_JWT_EXPIRE=7200
|
||||
@@ -163,3 +163,16 @@ func (c *AuthController) UpdateProfile(ctx *gin.Context) {
|
||||
"user": profile,
|
||||
})
|
||||
}
|
||||
|
||||
// Logout 登出
|
||||
// @Summary 用户登出
|
||||
// @Description 用户登出(JWT 无状态,前端清理令牌即可)
|
||||
// @Tags 认证
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Success 200 {object} models.BaseResponse
|
||||
// @Failure 401 {object} models.ErrorResponse
|
||||
// @Router /auth/logout [post]
|
||||
func (c *AuthController) Logout(ctx *gin.Context) {
|
||||
SuccessResponse(ctx, "登出成功")
|
||||
}
|
||||
|
||||
@@ -198,3 +198,25 @@ func (c *CompaniesController) Delete(ctx *gin.Context) {
|
||||
"message": "企业删除成功",
|
||||
})
|
||||
}
|
||||
|
||||
// StatsOverview 获取企业统计概览
|
||||
// @Summary 获取企业统计概览
|
||||
// @Description 获取企业、企业赋码、员工赋码的统计数据
|
||||
// @Tags 企业管理
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Success 200 {object} models.DataResponse
|
||||
// @Failure 401 {object} models.ErrorResponse
|
||||
// @Failure 500 {object} models.ErrorResponse
|
||||
// @Router /companies/stats/overview [get]
|
||||
func (c *CompaniesController) StatsOverview(ctx *gin.Context) {
|
||||
stats, err := c.companiesService.GetStatsOverview()
|
||||
if err != nil {
|
||||
ErrorResponse(ctx, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
SuccessResponse(ctx, "获取企业统计概览成功", gin.H{
|
||||
"overview": stats,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
@@ -118,4 +119,49 @@ func AutoMigrate() {
|
||||
}
|
||||
|
||||
logger.Info("数据库迁移成功")
|
||||
|
||||
// 创建默认管理员用户
|
||||
seedAdminUser()
|
||||
}
|
||||
|
||||
// seedAdminUser 创建默认管理员用户
|
||||
func seedAdminUser() {
|
||||
var count int64
|
||||
DB.Model(&models.User{}).Count(&count)
|
||||
|
||||
if count > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// 默认管理员账号
|
||||
adminUsername := "admin"
|
||||
adminPassword := "Beifan@2026"
|
||||
adminName := "管理员"
|
||||
adminEmail := "admin@example.com"
|
||||
|
||||
// 加密密码
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(adminPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
logger.Warn("管理员密码加密失败", logger.Err(err))
|
||||
return
|
||||
}
|
||||
|
||||
admin := models.User{
|
||||
Username: adminUsername,
|
||||
Password: string(hashedPassword),
|
||||
Name: adminName,
|
||||
Email: adminEmail,
|
||||
Role: "admin",
|
||||
}
|
||||
|
||||
if err := DB.Create(&admin).Error; err != nil {
|
||||
logger.Warn("创建默认管理员用户失败", logger.Err(err))
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("已创建默认管理员用户",
|
||||
logger.String("username", adminUsername),
|
||||
logger.String("password", adminPassword),
|
||||
logger.String("role", "admin"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -164,6 +164,19 @@ type CompanyUpdateRequest struct {
|
||||
IsActive *bool `json:"isActive"`
|
||||
}
|
||||
|
||||
// CompanyStatsOverviewDTO 企业统计概览
|
||||
type CompanyStatsOverviewDTO struct {
|
||||
TotalCompanies int64 `json:"totalCompanies"`
|
||||
ActiveCompanies int64 `json:"activeCompanies"`
|
||||
InactiveCompanies int64 `json:"inactiveCompanies"`
|
||||
TotalSerials int64 `json:"totalSerials"`
|
||||
ActiveSerials int64 `json:"activeSerials"`
|
||||
RevokedSerials int64 `json:"revokedSerials"`
|
||||
TotalEmployeeSerials int64 `json:"totalEmployeeSerials"`
|
||||
ActiveEmployeeSerials int64 `json:"activeEmployeeSerials"`
|
||||
RevokedEmployeeSerials int64 `json:"revokedEmployeeSerials"`
|
||||
}
|
||||
|
||||
// EmployeeSerial 员工序列号模型
|
||||
type EmployeeSerial struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
|
||||
@@ -25,6 +25,7 @@ func SetupAPIRoutes(r *gin.RouterGroup) {
|
||||
authRoutes := r.Group("/auth")
|
||||
{
|
||||
authRoutes.POST("/login", authController.Login)
|
||||
authRoutes.POST("/logout", middleware.JWTAuthMiddleware(), authController.Logout)
|
||||
authRoutes.GET("/profile", middleware.JWTAuthMiddleware(), authController.GetProfile)
|
||||
authRoutes.PUT("/profile", middleware.JWTAuthMiddleware(), authController.UpdateProfile)
|
||||
authRoutes.POST("/change-password", middleware.JWTAuthMiddleware(), authController.ChangePassword)
|
||||
@@ -47,6 +48,7 @@ func SetupAPIRoutes(r *gin.RouterGroup) {
|
||||
companiesController := controllers.NewCompaniesController()
|
||||
companiesRoutes := r.Group("/companies")
|
||||
{
|
||||
companiesRoutes.GET("/stats/overview", middleware.JWTAuthMiddleware(), companiesController.StatsOverview)
|
||||
companiesRoutes.GET("/", middleware.JWTAuthMiddleware(), companiesController.FindAll)
|
||||
companiesRoutes.POST("/", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), companiesController.Create)
|
||||
companiesRoutes.PUT("/:companyName", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), companiesController.Update)
|
||||
|
||||
@@ -110,3 +110,40 @@ func (s *CompaniesService) Delete(companyName string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetStatsOverview 获取企业统计概览
|
||||
func (s *CompaniesService) GetStatsOverview() (*models.CompanyStatsOverviewDTO, error) {
|
||||
stats := &models.CompanyStatsOverviewDTO{}
|
||||
|
||||
if err := database.DB.Model(&models.Company{}).Count(&stats.TotalCompanies).Error; err != nil {
|
||||
return nil, errors.New("统计企业总数失败")
|
||||
}
|
||||
|
||||
if err := database.DB.Model(&models.Company{}).Where("is_active = ?", true).Count(&stats.ActiveCompanies).Error; err != nil {
|
||||
return nil, errors.New("统计启用企业数量失败")
|
||||
}
|
||||
|
||||
stats.InactiveCompanies = stats.TotalCompanies - stats.ActiveCompanies
|
||||
|
||||
if err := database.DB.Model(&models.Serial{}).Count(&stats.TotalSerials).Error; err != nil {
|
||||
return nil, errors.New("统计企业赋码总数失败")
|
||||
}
|
||||
|
||||
if err := database.DB.Model(&models.Serial{}).Where("is_active = ?", true).Count(&stats.ActiveSerials).Error; err != nil {
|
||||
return nil, errors.New("统计有效企业赋码数量失败")
|
||||
}
|
||||
|
||||
stats.RevokedSerials = stats.TotalSerials - stats.ActiveSerials
|
||||
|
||||
if err := database.DB.Model(&models.EmployeeSerial{}).Count(&stats.TotalEmployeeSerials).Error; err != nil {
|
||||
return nil, errors.New("统计员工赋码总数失败")
|
||||
}
|
||||
|
||||
if err := database.DB.Model(&models.EmployeeSerial{}).Where("is_active = ?", true).Count(&stats.ActiveEmployeeSerials).Error; err != nil {
|
||||
return nil, errors.New("统计有效员工赋码数量失败")
|
||||
}
|
||||
|
||||
stats.RevokedEmployeeSerials = stats.TotalEmployeeSerials - stats.ActiveEmployeeSerials
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
@@ -773,3 +773,47 @@ func TestEmployeeSerialsService_GenerateQRCode_Inactive(t *testing.T) {
|
||||
database.DB.Unscoped().Where("company_name = ?", "QREmpCompany2").Delete(&models.Company{})
|
||||
database.DB.Unscoped().Delete(&user)
|
||||
}
|
||||
|
||||
func TestCompaniesService_GetStatsOverview_Success(t *testing.T) {
|
||||
var user models.User
|
||||
password, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
|
||||
user = models.User{
|
||||
Username: "statsadmin",
|
||||
Password: string(password),
|
||||
Name: "统计管理员",
|
||||
Email: "statsadmin@example.com",
|
||||
Role: "admin",
|
||||
}
|
||||
database.DB.Create(&user)
|
||||
|
||||
serialService := SerialsService{}
|
||||
companySerials, _ := serialService.Generate("StatsCompany", 2, 30, user.ID, "STAT")
|
||||
|
||||
employeeSerialsService := EmployeeSerialsService{}
|
||||
employeeSerials, _ := employeeSerialsService.Generate("StatsCompany", "技术部", "测试员工", 2, user.ID)
|
||||
|
||||
_ = serialService.Revoke(companySerials[0].SerialNumber)
|
||||
_ = employeeSerialsService.Revoke(employeeSerials[0].SerialNumber)
|
||||
|
||||
companiesService := CompaniesService{}
|
||||
stats, err := companiesService.GetStatsOverview()
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, stats)
|
||||
assert.GreaterOrEqual(t, stats.TotalCompanies, int64(1))
|
||||
assert.GreaterOrEqual(t, stats.TotalSerials, int64(2))
|
||||
assert.GreaterOrEqual(t, stats.TotalEmployeeSerials, int64(2))
|
||||
assert.GreaterOrEqual(t, stats.RevokedSerials, int64(1))
|
||||
assert.GreaterOrEqual(t, stats.RevokedEmployeeSerials, int64(1))
|
||||
|
||||
for _, serial := range companySerials {
|
||||
database.DB.Unscoped().Delete(&serial)
|
||||
}
|
||||
|
||||
for _, serial := range employeeSerials {
|
||||
database.DB.Unscoped().Delete(&serial)
|
||||
}
|
||||
|
||||
database.DB.Unscoped().Where("company_name = ?", "StatsCompany").Delete(&models.Company{})
|
||||
database.DB.Unscoped().Delete(&user)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user