基于 React + Go + PostgreSQL + Redis 的管理系统开发框架
siqian-admin 开发框架
https://github.com/KakaCheng2010/siqian-admin.git
基于 React + Go + PostgreSQL + Redis 的管理系统开发框架
🚀 技术栈
后端
- Go 1.21+ - 主要后端语言
- Gin - Web框架
- GORM - ORM框架
- PostgreSQL - 主数据库
- Redis - 缓存和会话存储
- JWT - 身份认证
- Viper - 配置管理
前端
- React 18 - 前端框架
- TypeScript - 类型安全
- Ant Design - UI组件库
- React Router - 路由管理
- Axios - HTTP客户端
- Zustand - 状态管理
📋 功能模块
系统管理模块 (sys/)
- ✅ 用户管理 - 用户CRUD、状态管理、角色分配
- ✅ 组织管理 - 树形组织结构、层级管理
- ✅ 角色管理 - 角色定义、权限分配
- ✅ 菜单管理 - 动态菜单、权限控制
- ✅ 字典管理 - 系统字典、数据字典项
认证模块 (auth/)
- ✅ 用户认证 - JWT令牌认证
- ✅ 权限控制 - 基于角色的访问控制
- ✅ 会话管理 - Redis会话存储
🏗️ 项目结构
siqian-admin/
├── backend/ # Go后端
│ ├── cmd/ # 应用入口
│ │ └── main.go
│ ├── internal/ # 内部包
│ │ ├── api/ # API层
│ │ ├── config/ # 配置管理
│ │ ├── database/ # 数据库连接
│ │ ├── middleware/ # 中间件
│ │ ├── router/ # 路由配置
│ │ ├── service/ # 业务逻辑层
│ │ ├── sys/ # 系统管理模块
│ │ │ ├── api/ # 系统管理API
│ │ │ ├── model/ # 数据模型
│ │ │ └── service/ # 业务逻辑
│ │ └── utils/ # 工具函数
│ ├── config.yaml # 配置文件
│ ├── Dockerfile
│ ├── go.mod
│ └── go.sum
├── frontend/ # React前端
│ ├── src/
│ │ ├── components/ # 公共组件
│ │ ├── pages/ # 页面组件
│ │ ├── services/ # API服务
│ │ ├── store/ # 状态管理
│ │ ├── router/ # 路由配置
│ │ ├── hooks/ # 自定义Hooks
│ │ └── utils/ # 工具函数
│ ├── package.json
│ └── Dockerfile
└── README.md
🚀 快速开始
环境要求
- Go 1.21+
- Node.js 18+
- Docker & Docker Compose
- Git
配置文件
数据库文件位于 backend/scripts/siqian-admin.sql
项目配置文件位于 backend/config.yaml:
server:port: "8080" # 服务器端口mode: "debug" # 运行模式database:host: "localhost" # 数据库主机port: 5432 # 数据库端口user: "postgres" # 数据库用户名password: "password" # 数据库密码dbname: "go_admin" # 数据库名称sslmode: "disable" # SSL模式redis:host: "localhost" # Redis主机port: 6379 # Redis端口password: "" # Redis密码db: 0 # Redis数据库jwt:secret: "your-secret-key" # JWT密钥expire_time: 24 # 过期时间(小时)
启动项目
# 1. 启动数据库和Redis
docker-compose up -d postgres redis# 2. 启动后端
cd backend
go mod tidy
go run cmd/main.go# 3. 启动前端
cd frontend
npm install
npm run dev
访问地址
- 前端: http://localhost:3000
- 后端API: http://localhost:8080
- 默认账户: admin / 123456
🛠️ 新功能开发指南
完整开发流程:从后端到前端
以添加"产品管理"模块为例,展示完整的开发流程:
1. 后端开发
步骤1:创建数据模型
// backend/internal/sys/model/product.go
package modelimport ("time""gorm.io/gorm"
)type Product struct {ID uint `json:"id" gorm:"primaryKey"`Name string `json:"name" gorm:"not null" binding:"required"`Code string `json:"code" gorm:"uniqueIndex;not null" binding:"required"`Price float64 `json:"price" gorm:"not null"`Category string `json:"category"`Status int `json:"status" gorm:"default:1"` // 1:正常 0:禁用Description string `json:"description"`CreatedAt time.Time `json:"created_at"`UpdatedAt time.Time `json:"updated_at"`DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
}func (Product) TableName() string {return "sys_products"
}
步骤2:创建业务逻辑层
// backend/internal/sys/service/product.go
package serviceimport ("siqian-admin/internal/sys/model""gorm.io/gorm"
)type ProductService struct {db *gorm.DB
}func NewProductService(db *gorm.DB) *ProductService {return &ProductService{db: db}
}func (s *ProductService) CreateProduct(product *model.Product) error {return s.db.Create(product).Error
}func (s *ProductService) GetProductByID(id uint) (*model.Product, error) {var product model.Producterr := s.db.First(&product, id).Errorreturn &product, err
}func (s *ProductService) UpdateProduct(product *model.Product) error {return s.db.Save(product).Error
}func (s *ProductService) DeleteProduct(id uint) error {return s.db.Delete(&model.Product{}, id).Error
}func (s *ProductService) ListProducts(page, pageSize int) ([]model.Product, int64, error) {var products []model.Productvar total int64offset := (page - 1) * pageSizeerr := s.db.Model(&model.Product{}).Count(&total).Errorif err != nil {return nil, 0, err}err = s.db.Offset(offset).Limit(pageSize).Find(&products).Errorreturn products, total, err
}
步骤3:创建API层
// backend/internal/sys/api/product.go
package apiimport ("siqian-admin/internal/sys/model""siqian-admin/internal/sys/service""net/http""strconv""github.com/gin-gonic/gin"
)type ProductHandler struct {productService *service.ProductService
}func NewProductHandler(productService *service.ProductService) *ProductHandler {return &ProductHandler{productService: productService}
}type CreateProductRequest struct {Name string `json:"name" binding:"required"`Code string `json:"code" binding:"required"`Price float64 `json:"price" binding:"required"`Category string `json:"category"`Status int `json:"status"`Description string `json:"description"`
}func (h *ProductHandler) CreateProduct(c *gin.Context) {var req CreateProductRequestif err := c.ShouldBindJSON(&req); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}product := &model.Product{Name: req.Name,Code: req.Code,Price: req.Price,Category: req.Category,Status: req.Status,Description: req.Description,}if err := h.productService.CreateProduct(product); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}c.JSON(http.StatusCreated, gin.H{"message": "产品创建成功", "product": product})
}func (h *ProductHandler) GetProduct(c *gin.Context) {id, err := strconv.ParseUint(c.Param("id"), 10, 32)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": "无效的产品ID"})return}product, err := h.productService.GetProductByID(uint(id))if err != nil {c.JSON(http.StatusNotFound, gin.H{"error": "产品不存在"})return}c.JSON(http.StatusOK, product)
}func (h *ProductHandler) ListProducts(c *gin.Context) {page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))products, total, err := h.productService.ListProducts(page, pageSize)if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"error": "获取产品列表失败"})return}c.JSON(http.StatusOK, gin.H{"products": products,"total": total,"page": page,"page_size": pageSize,})
}func (h *ProductHandler) UpdateProduct(c *gin.Context) {id, err := strconv.ParseUint(c.Param("id"), 10, 32)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": "无效的产品ID"})return}var req CreateProductRequestif err := c.ShouldBindJSON(&req); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}product, err := h.productService.GetProductByID(uint(id))if err != nil {c.JSON(http.StatusNotFound, gin.H{"error": "产品不存在"})return}product.Name = req.Nameproduct.Code = req.Codeproduct.Price = req.Priceproduct.Category = req.Categoryproduct.Status = req.Statusproduct.Description = req.Descriptionif err := h.productService.UpdateProduct(product); err != nil {c.JSON(http.StatusInternalServerError, gin.H{"error": "更新失败"})return}c.JSON(http.StatusOK, gin.H{"message": "更新成功", "product": product})
}func (h *ProductHandler) DeleteProduct(c *gin.Context) {id, err := strconv.ParseUint(c.Param("id"), 10, 32)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": "无效的产品ID"})return}if err := h.productService.DeleteProduct(uint(id)); err != nil {c.JSON(http.StatusInternalServerError, gin.H{"error": "删除失败"})return}c.JSON(http.StatusOK, gin.H{"message": "删除成功"})
}
步骤4:注册路由
// backend/internal/router/router.go
// 在路由配置中添加产品管理路由
func SetupRoutes(r *gin.Engine, db *gorm.DB, rdb *redis.Client) {// ... 其他路由配置// 产品管理productService := sysservice.NewProductService(db)productHandler := sysapi.NewProductHandler(productService)products := authorized.Group("/products"){products.POST("", productHandler.CreateProduct)products.GET("", productHandler.ListProducts)products.GET("/:id", productHandler.GetProduct)products.PUT("/:id", productHandler.UpdateProduct)products.DELETE("/:id", productHandler.DeleteProduct)}
}
2. 前端开发
步骤1:创建API服务
// frontend/src/services/product.ts
import api from './api';export interface Product {id: number;name: string;code: string;price: number;category: string;status: number;description: string;created_at: string;updated_at: string;
}export interface CreateProductRequest {name: string;code: string;price: number;category: string;status: number;description: string;
}export interface ProductListResponse {products: Product[];total: number;page: number;page_size: number;
}export const productService = {getProducts: async (page = 1, pageSize = 10): Promise<ProductListResponse> => {const response = await api.get('/products', {params: { page, page_size: pageSize }});return response.data;},getProduct: async (id: string): Promise<Product> => {const response = await api.get(`/products/${id}`);return response.data;},createProduct: async (data: CreateProductRequest): Promise<Product> => {const response = await api.post('/products', data);return response.data.product;},updateProduct: async (id: string, data: CreateProductRequest): Promise<Product> => {const response = await api.put(`/products/${id}`, data);return response.data.product;},deleteProduct: async (id: string): Promise<void> => {await api.delete(`/products/${id}`);},
};
步骤2:创建页面组件
// frontend/src/pages/product/ProductManagement.tsx
import React, { useState, useEffect } from 'react';
import { Card, Table, Button, Space, Tag, message, Popconfirm, Modal, Form, Input, InputNumber, Select } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined, ReloadOutlined } from '@ant-design/icons';
import { productService, Product, CreateProductRequest } from '../../services/product';const ProductManagement: React.FC = () => {const [products, setProducts] = useState<Product[]>([]);const [loading, setLoading] = useState(false);const [createVisible, setCreateVisible] = useState(false);const [editVisible, setEditVisible] = useState(false);const [editingProduct, setEditingProduct] = useState<Product | null>(null);const [form] = Form.useForm<CreateProductRequest>();const [editForm] = Form.useForm<CreateProductRequest>();// 加载产品列表const loadProducts = async () => {setLoading(true);try {const data = await productService.getProducts();setProducts(data.products);} catch (error) {message.error('加载产品列表失败');} finally {setLoading(false);}};useEffect(() => {loadProducts();}, []);// 打开新增弹窗const openCreateModal = () => {form.resetFields();form.setFieldsValue({ status: 1 });setCreateVisible(true);};// 提交新增const handleCreate = async () => {try {const values = await form.validateFields();await productService.createProduct(values);message.success('创建成功');setCreateVisible(false);loadProducts();} catch (error: any) {if (error?.errorFields) return;message.error(error?.response?.data?.error || '创建失败');}};// 打开编辑弹窗const openEditModal = (product: Product) => {setEditingProduct(product);editForm.setFieldsValue({name: product.name,code: product.code,price: product.price,category: product.category,status: product.status,description: product.description,});setEditVisible(true);};// 提交编辑const handleEdit = async () => {if (!editingProduct) return;try {const values = await editForm.validateFields();await productService.updateProduct(editingProduct.id.toString(), values);message.success('更新成功');setEditVisible(false);loadProducts();} catch (error: any) {if (error?.errorFields) return;message.error(error?.response?.data?.error || '更新失败');}};// 删除产品const handleDelete = async (id: string) => {try {await productService.deleteProduct(id);message.success('删除成功');loadProducts();} catch (error: any) {message.error(error?.response?.data?.error || '删除失败');}};const columns = [{title: '产品名称',dataIndex: 'name',key: 'name',},{title: '产品编码',dataIndex: 'code',key: 'code',},{title: '价格',dataIndex: 'price',key: 'price',render: (price: number) => `¥${price.toFixed(2)}`,},{title: '分类',dataIndex: 'category',key: 'category',},{title: '状态',dataIndex: 'status',key: 'status',render: (status: number) => (<Tag color={status === 1 ? 'green' : 'red'}>{status === 1 ? '正常' : '禁用'}</Tag>),},{title: '操作',key: 'action',render: (_, record: Product) => (<Space><Buttontype="link"icon={<EditOutlined />}onClick={() => openEditModal(record)}>编辑</Button><Popconfirmtitle="确定要删除这个产品吗?"onConfirm={() => handleDelete(record.id.toString())}okText="确定"cancelText="取消"><Button type="link" danger icon={<DeleteOutlined />}>删除</Button></Popconfirm></Space>),},];return (<Card title="产品管理"><div style={{ marginBottom: 16 }}><Space><Button type="primary" icon={<PlusOutlined />} onClick={openCreateModal}>新增产品</Button><Button icon={<ReloadOutlined />} onClick={loadProducts}>刷新</Button></Space></div><Tablecolumns={columns}dataSource={products}rowKey="id"loading={loading}pagination={{showSizeChanger: true,showQuickJumper: true,showTotal: (total) => `共 ${total} 条记录`,}}/>{/* 新增弹窗 */}<Modaltitle="新增产品"open={createVisible}onOk={handleCreate}onCancel={() => setCreateVisible(false)}okText="确定"cancelText="取消"><Form form={form} layout="vertical"><Form.Item name="name" label="产品名称" rules={[{ required: true, message: '请输入产品名称' }]}><Input placeholder="请输入产品名称" /></Form.Item><Form.Item name="code" label="产品编码" rules={[{ required: true, message: '请输入产品编码' }]}><Input placeholder="请输入产品编码" /></Form.Item><Form.Item name="price" label="价格" rules={[{ required: true, message: '请输入价格' }]}><InputNumber placeholder="请输入价格" style={{ width: '100%' }} min={0} /></Form.Item><Form.Item name="category" label="分类"><Input placeholder="请输入分类" /></Form.Item><Form.Item name="status" label="状态"><Select><Select.Option value={1}>正常</Select.Option><Select.Option value={0}>禁用</Select.Option></Select></Form.Item><Form.Item name="description" label="描述"><Input.TextArea placeholder="请输入描述" rows={3} /></Form.Item></Form></Modal>{/* 编辑弹窗 */}<Modaltitle="编辑产品"open={editVisible}onOk={handleEdit}onCancel={() => setEditVisible(false)}okText="确定"cancelText="取消"><Form form={editForm} layout="vertical"><Form.Item name="name" label="产品名称" rules={[{ required: true, message: '请输入产品名称' }]}><Input placeholder="请输入产品名称" /></Form.Item><Form.Item name="code" label="产品编码" rules={[{ required: true, message: '请输入产品编码' }]}><Input placeholder="请输入产品编码" /></Form.Item><Form.Item name="price" label="价格" rules={[{ required: true, message: '请输入价格' }]}><InputNumber placeholder="请输入价格" style={{ width: '100%' }} min={0} /></Form.Item><Form.Item name="category" label="分类"><Input placeholder="请输入分类" /></Form.Item><Form.Item name="status" label="状态"><Select><Select.Option value={1}>正常</Select.Option><Select.Option value={0}>禁用</Select.Option></Select></Form.Item><Form.Item name="description" label="描述"><Input.TextArea placeholder="请输入描述" rows={3} /></Form.Item></Form></Modal></Card>);
};export default ProductManagement;
步骤3:添加路由
在菜单管理配置菜单并赋给角色,重新登录后即可访问
🔐 权限控制使用指南
后端权限控制
1. 中间件权限控制
// 在路由中使用权限中间件
users.GET("", middleware.AuthMiddleware(rdb, []string{"user:list"}), userHandler.ListUsers)
users.POST("", middleware.AuthMiddleware(rdb, []string{"user:create"}), userHandler.CreateUser)
users.PUT("/:id", middleware.AuthMiddleware(rdb, []string{"user:update"}), userHandler.UpdateUser)
users.DELETE("/:id", middleware.AuthMiddleware(rdb, []string{"user:delete"}), userHandler.DeleteUser)
2. 权限标识规范
- 格式:
模块:操作 - 示例:
user:list、user:create、user:update、user:delete - 菜单权限:
user:menu
前端权限控制
// 在组件中使用权限控制
import { usePermission } from '../utils/permission';const UserManagement: React.FC = () => {const canCreate = usePermission('user:create');const canUpdate = usePermission('user:update');const canDelete = usePermission('user:delete');return (<div>{canCreate && (<Button type="primary" onClick={handleCreate}>新增用户</Button>)}<Tablecolumns={[// ... 其他列{title: '操作',render: (_, record) => (<Space>{canUpdate && (<Button onClick={() => handleEdit(record)}>编辑</Button>)}{canDelete && (<Button danger onClick={() => handleDelete(record)}>删除</Button>)}</Space>),},]}/></div>);
};
3. 路由权限控制
// 在路由配置中使用权限控制
import { usePermission } from '../utils/permission';const ProtectedRoute: React.FC<{ permission: string; children: React.ReactNode }> = ({permission,children,
}) => {const hasPermission = usePermission(permission);if (!hasPermission) {return <div>无权限访问</div>;}return <>{children}</>;
};// 使用示例
<Route path="/users" element={<ProtectedRoute permission="user:menu"><UserManagement /></ProtectedRoute>}
/>
前端字典使用
1. 在组件中使用字典
// 使用字典数据
import { useDict } from '../hooks/useDict';
import { useDictStore } from '../store/dictStore';const UserForm: React.FC = () => {// 方式1:使用Hookconst { dictOptions: statusOptions } = useDict('user_status');// 方式2:直接从Store获取const { getDictOptions } = useDictStore();const genderOptions = getDictOptions('user_gender');return (<Form><Form.Item name="status" label="状态"><Select options={statusOptions} placeholder="请选择状态" /></Form.Item><Form.Item name="gender" label="性别"><Select options={genderOptions} placeholder="请选择性别" /></Form.Item></Form>);
};
🧩 组件使用指南
1. OrganizationTree组件
// 使用组织树组件
import OrganizationTree from '../components/OrganizationTree';const MyComponent: React.FC = () => {const [selectedOrgId, setSelectedOrgId] = useState<string | null>(null);return (<OrganizationTreeselectedOrgId={selectedOrgId}onSelect={setSelectedOrgId}defaultExpandAll={true}title="选择组织"size="default"/>);
};
2. UserSelector组件
// 使用用户选择器组件
import UserSelector from '../components/UserSelector';const MyComponent: React.FC = () => {const [userSelectorVisible, setUserSelectorVisible] = useState(false);const [selectedUserKeys, setSelectedUserKeys] = useState<React.Key[]>([]);const handleUserSelect = (userIds: string[]) => {console.log('选中的用户ID:', userIds);setUserSelectorVisible(false);};return (<div><Button onClick={() => setUserSelectorVisible(true)}>选择用户</Button><UserSelectorvisible={userSelectorVisible}title="选择用户"selectedUserKeys={selectedUserKeys}onCancel={() => setUserSelectorVisible(false)}onOk={handleUserSelect}/></div>);
};
🖼️ 效果展示








