Appearance
DSPlatform 开发指南
概述
DSPlatform 是一个基于现代化技术栈构建的多端电商管理系统,支持 PC 管理后台、H5、微信小程序、APP 等多个平台。本指南将帮助开发者快速上手 DSPlatform 的开发工作。
系统架构
技术栈
后端技术
- 框架: ThinkPHP 8.0
- 语言: PHP 8.0+
- 数据库: MySQL 8.0+
- 缓存: Redis 6.0+
- 文件存储: 本地存储 / 阿里云 OSS
前端技术
- 管理后台: Vue 3 + Element Plus + Vite
- 移动端: UniApp + Vue 3
- 构建工具: Vite 5.2+
- 状态管理: Pinia
- HTTP 客户端: Axios
- 样式: SCSS + Tailwind CSS
第三方集成
- 支付: 微信支付、支付宝
- 短信: 阿里云短信、腾讯云短信
- 地图: 腾讯地图、高德地图、百度地图
开发环境搭建
环境要求
开发工具
- IDE: VS Code / PhpStorm / WebStorm
- PHP: 8.0+ (推荐 8.1+)
- Node.js: 16.0+ (推荐 18.0+)
- MySQL: 8.0+
- Redis: 6.0+
- Git: 2.0+
PHP 扩展要求
bash
# 必需扩展
php-mysql
php-redis
php-gd
php-curl
php-mbstring
php-xml
php-zip
php-openssl
php-fileinfo
php-json
php-tokenizer
php-pdo
php-bcmath
php-intl
项目初始化
1. 获取源码
bash
# 克隆项目
git clone https://github.com/your-org/dsplatform.git
cd dsplatform
# 安装后端依赖
cd php-server
composer install
# 安装前端依赖
cd ../vue-element-admin
npm install
cd ../uniapp
npm install
2. 开发环境配置
后端配置 (php-server/.env
):
env
# 开发环境配置
APP_DEBUG = true
APP_TRACE = true
# 数据库配置
DATABASE_TYPE = mysql
DATABASE_HOSTNAME = 127.0.0.1
DATABASE_DATABASE = dsplatform_dev
DATABASE_USERNAME = root
DATABASE_PASSWORD = your_password
DATABASE_HOSTPORT = 3306
DATABASE_CHARSET = utf8mb4
DATABASE_PREFIX = ds_
# Redis 配置
REDIS_HOSTNAME = 127.0.0.1
REDIS_PORT = 6379
REDIS_PASSWORD =
REDIS_SELECT = 0
# JWT 配置
JWT_SECRET_KEY = dev_jwt_secret_key_here
前端配置 (vue-element-admin/.env.development
):
env
# API 基础地址
VITE_API_BASE_URL=http://localhost:8000
# 应用标题
VITE_APP_TITLE=DSPlatform管理后台
# 是否开启代理
VITE_USE_PROXY=false
移动端配置 (uniapp/.env.development
):
env
# API 基础地址
VITE_API_BASE_URL=http://localhost:8000
# 应用标题
VITE_APP_TITLE=DSPlatform
# 微信小程序 AppID
VITE_WECHAT_APPID=your_wechat_appid
开发服务器启动
后端服务
bash
cd php-server
# 使用 PHP 内置服务器(开发环境)
php think run
# 或使用 Nginx + PHP-FPM
# 配置虚拟主机指向 public 目录
管理后台
bash
cd vue-element-admin
# 启动开发服务器
npm run dev
# 访问地址: http://localhost:3000
移动端
bash
cd uniapp
# H5 开发
npm run dev:h5
# 微信小程序开发
npm run dev:mp-weixin
# APP 开发
npm run dev:app
开发规范
代码规范
PHP 代码规范
- 遵循 PSR-12 编码规范
- 使用 PSR-4 自动加载规范
- 类名使用 PascalCase
- 方法名和变量名使用 camelCase
- 常量使用 UPPER_SNAKE_CASE
JavaScript/Vue 代码规范
- 使用 ESLint + Prettier 格式化
- 遵循 Vue 3 Composition API 规范
- 使用 TypeScript 进行类型检查
- 组件名使用 PascalCase
- 文件名使用 kebab-case
项目结构
后端项目结构
php-server/
├── app/ # 应用目录
│ ├── adminapi/ # 管理端API
│ ├── api/ # 用户端API
│ ├── merchantapi/ # 商户端API
│ ├── storeapi/ # 店铺端API
│ ├── riderapi/ # 骑手端API
│ ├── techapi/ # 技师端API
│ ├── bloggerapi/ # 博主端API
│ ├── common/ # 公共模块
│ ├── deshang/ # 核心业务模块
│ └── crontab/ # 定时任务
├── config/ # 配置文件
├── public/ # 公共资源
├── route/ # 路由定义
├── vendor/ # 第三方库
└── runtime/ # 运行时文件
前端项目结构
vue-element-admin/
├── src/
│ ├── api/ # API 接口
│ ├── assets/ # 静态资源
│ ├── components/ # 公共组件
│ ├── hooks/ # 组合式函数
│ ├── layout/ # 布局组件
│ ├── pages-admin/ # 管理端页面
│ ├── pages-store/ # 店铺端页面
│ ├── pages-merchant/ # 商户端页面
│ ├── pages-consumer/ # 用户端页面
│ ├── router/ # 路由配置
│ ├── stores/ # 状态管理
│ ├── styles/ # 样式文件
│ └── utils/ # 工具函数
├── public/ # 公共资源
└── dist/ # 构建输出
移动端项目结构
uniapp/
├── src/
│ ├── api/ # API 接口
│ ├── components/ # 公共组件
│ ├── home/ # 用户端模块
│ ├── merchant/ # 商户端模块
│ ├── store/ # 店铺端模块
│ ├── rider/ # 骑手端模块
│ ├── technician/ # 技师端模块
│ ├── video/ # 视频端模块
│ ├── shared/ # 共享模块
│ ├── stores/ # 状态管理
│ ├── utils/ # 工具函数
│ └── styles/ # 样式文件
├── static/ # 静态资源
└── unpackage/ # 构建输出
Git 工作流
分支管理
main
: 主分支,用于生产环境develop
: 开发分支,用于集成开发feature/*
: 功能分支,用于新功能开发hotfix/*
: 热修复分支,用于紧急修复
提交规范
bash
# 提交格式
<type>(<scope>): <subject>
# 类型说明
feat: 新功能
fix: 修复问题
docs: 文档更新
style: 代码格式调整
refactor: 代码重构
test: 测试相关
chore: 构建过程或辅助工具的变动
# 示例
feat(user): 添加用户注册功能
fix(order): 修复订单支付问题
docs(api): 更新API文档
数据库规范
表命名规范
- 表名使用
ds_
前缀 - 表名使用小写字母和下划线
- 字段名使用小写字母和下划线
- 主键统一使用
id
- 创建时间字段使用
create_at
- 更新时间字段使用
update_at
- 软删除字段使用
is_deleted
索引规范
- 主键索引:
PRIMARY KEY
- 唯一索引:
UNIQUE KEY
- 普通索引:
KEY
- 外键索引:
FOREIGN KEY
开发指南
后端开发
API 开发
控制器开发:
php
// app/api/controller/user/User.php
namespace app\api\controller\user;
use app\deshang\base\controller\BaseUserController;
use app\api\service\user\UserService;
class User extends BaseUserController
{
/**
* 获取用户信息
* @OA\Get(
* path="/api/user/user/info",
* tags={"api/user/User"},
* summary="获取用户信息",
* description="获取当前登录用户的基础信息",
* @OA\Response(
* response=200,
* description="操作成功",
* @OA\JsonContent(
* @OA\Property(property="code", type="integer", example=10000),
* @OA\Property(property="msg", type="string", example="操作成功"),
* @OA\Property(property="data", type="object")
* )
* )
* )
*/
public function getUserInfo()
{
$result = (new UserService())->getUserInfo();
return ds_json_success('操作成功', $result);
}
}
服务层开发:
php
// app/api/service/user/UserService.php
namespace app\api\service\user;
use app\deshang\base\service\BaseUserService;
use app\common\dao\user\UserDao;
class UserService extends BaseUserService
{
public function getUserInfo()
{
$user_info = (new UserDao())->getUserInfo(['id' => $this->user_id]);
unset($user_info['password']);
unset($user_info['pay_password']);
return $user_info;
}
}
路由配置:
php
// app/api/route/user.php
use think\facade\Route;
use app\api\middleware\UserAuthorizeToken;
use app\api\middleware\UserAuthorizeLog;
Route::group('user', function () {
Route::get('user/info', 'user.User/getUserInfo');
Route::post('user/info', 'user.User/updateUserInfo');
})->middleware([
UserAuthorizeToken::class,
UserAuthorizeLog::class,
]);
数据库开发
模型开发:
php
// app/common/model/user/UserModel.php
namespace app\common\model\user;
use app\deshang\base\BaseModel;
class UserModel extends BaseModel
{
protected $table = 'ds_user';
protected $hidden = ['password', 'pay_password'];
protected $append = ['user_status_desc'];
public function getUserStatusDescAttr($value, $data)
{
$status = [
0 => '禁用',
1 => '启用',
];
return $status[$data['is_enabled']] ?? '未知';
}
}
DAO 开发:
php
// app/common/dao/user/UserDao.php
namespace app\common\dao\user;
use app\common\dao\BaseDao;
use app\common\model\user\UserModel;
class UserDao extends BaseDao
{
public function __construct()
{
parent::__construct();
$this->model = new UserModel();
}
public function getUserInfo($condition)
{
return $this->model->where($condition)->findOrEmpty()->toArray();
}
}
前端开发
管理后台开发
页面组件:
vue
<!-- src/pages-admin/user/UserList.vue -->
<template>
<div class="user-list">
<el-card>
<template #header>
<div class="card-header">
<span>用户列表</span>
<el-button type="primary" @click="handleAdd">添加用户</el-button>
</div>
</template>
<el-table :data="tableData" style="width: 100%" v-loading="loading">
<el-table-column prop="username" label="用户名" />
<el-table-column prop="mobile" label="手机号" />
<el-table-column prop="user_status_desc" label="状态" />
<el-table-column prop="create_at" label="创建时间" />
<el-table-column label="操作" width="200">
<template #default="scope">
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
<el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="pagination.page"
v-model:page-size="pagination.size"
:total="pagination.total"
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getUserList, deleteUser } from '@/api/user'
const loading = ref(false)
const tableData = ref([])
const pagination = ref({
page: 1,
size: 10,
total: 0
})
const fetchData = async () => {
loading.value = true
try {
const res = await getUserList({
page: pagination.value.page,
size: pagination.value.size
})
tableData.value = res.data.list
pagination.value.total = res.data.total
} catch (error) {
ElMessage.error('获取数据失败')
} finally {
loading.value = false
}
}
const handleAdd = () => {
// 添加用户逻辑
}
const handleEdit = (row: any) => {
// 编辑用户逻辑
}
const handleDelete = async (row: any) => {
try {
await ElMessageBox.confirm('确定删除该用户吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
await deleteUser(row.id)
ElMessage.success('删除成功')
fetchData()
} catch (error) {
// 用户取消删除
}
}
const handlePageChange = (page: number) => {
pagination.value.page = page
fetchData()
}
const handleSizeChange = (size: number) => {
pagination.value.size = size
fetchData()
}
onMounted(() => {
fetchData()
})
</script>
API 接口:
typescript
// src/api/user.ts
import { request } from '@/utils/request'
export interface User {
id: number
username: string
mobile: string
is_enabled: number
create_at: string
}
export interface UserListParams {
page: number
size: number
username?: string
mobile?: string
}
export const getUserList = (params: UserListParams) => {
return request.get('/adminapi/user/list', { params })
}
export const getUserInfo = (id: number) => {
return request.get(`/adminapi/user/info/${id}`)
}
export const createUser = (data: Partial<User>) => {
return request.post('/adminapi/user/create', data)
}
export const updateUser = (id: number, data: Partial<User>) => {
return request.put(`/adminapi/user/update/${id}`, data)
}
export const deleteUser = (id: number) => {
return request.delete(`/adminapi/user/delete/${id}`)
}
移动端开发
页面组件:
vue
<!-- src/home/pages/index/index.vue -->
<template>
<view class="home">
<!-- 轮播图 -->
<view class="banner">
<swiper :indicator-dots="true" :autoplay="true" :interval="3000" :duration="1000">
<swiper-item v-for="item in banners" :key="item.id">
<image :src="item.image" mode="aspectFill" class="banner-image" />
</swiper-item>
</swiper>
</view>
<!-- 分类导航 -->
<view class="category-nav">
<view
class="category-item"
v-for="item in categories"
:key="item.id"
@click="goToCategory(item)"
>
<image :src="item.icon" class="category-icon" />
<text class="category-name">{{ item.name }}</text>
</view>
</view>
<!-- 商品列表 -->
<view class="goods-list">
<view
class="goods-item"
v-for="item in goodsList"
:key="item.id"
@click="goToGoods(item)"
>
<image :src="item.image" class="goods-image" />
<view class="goods-info">
<text class="goods-name">{{ item.name }}</text>
<text class="goods-price">¥{{ item.price }}</text>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { getBannerList, getCategoryList, getGoodsList } from '@/api/home'
const banners = ref([])
const categories = ref([])
const goodsList = ref([])
const fetchBanners = async () => {
try {
const res = await getBannerList()
banners.value = res.data
} catch (error) {
console.error('获取轮播图失败:', error)
}
}
const fetchCategories = async () => {
try {
const res = await getCategoryList()
categories.value = res.data
} catch (error) {
console.error('获取分类失败:', error)
}
}
const fetchGoods = async () => {
try {
const res = await getGoodsList({ page: 1, size: 10 })
goodsList.value = res.data.list
} catch (error) {
console.error('获取商品失败:', error)
}
}
const goToCategory = (category: any) => {
uni.navigateTo({
url: `/pages/category/index?id=${category.id}`
})
}
const goToGoods = (goods: any) => {
uni.navigateTo({
url: `/pages/goods/detail?id=${goods.id}`
})
}
onMounted(() => {
fetchBanners()
fetchCategories()
fetchGoods()
})
</script>
<style lang="scss" scoped>
.home {
padding: 20rpx;
}
.banner {
height: 300rpx;
margin-bottom: 20rpx;
.banner-image {
width: 100%;
height: 100%;
}
}
.category-nav {
display: flex;
flex-wrap: wrap;
margin-bottom: 20rpx;
.category-item {
width: 25%;
display: flex;
flex-direction: column;
align-items: center;
padding: 20rpx 0;
.category-icon {
width: 80rpx;
height: 80rpx;
margin-bottom: 10rpx;
}
.category-name {
font-size: 24rpx;
color: #333;
}
}
}
.goods-list {
display: flex;
flex-wrap: wrap;
.goods-item {
width: 48%;
margin: 1%;
background: #fff;
border-radius: 10rpx;
overflow: hidden;
.goods-image {
width: 100%;
height: 200rpx;
}
.goods-info {
padding: 20rpx;
.goods-name {
font-size: 28rpx;
color: #333;
margin-bottom: 10rpx;
}
.goods-price {
font-size: 32rpx;
color: #ff4444;
font-weight: bold;
}
}
}
}
</style>
API 接口:
typescript
// src/api/home.ts
import { request } from '@/utils/request'
export interface Banner {
id: number
title: string
image: string
link: string
}
export interface Category {
id: number
name: string
icon: string
}
export interface Goods {
id: number
name: string
image: string
price: number
original_price: number
}
export const getBannerList = () => {
return request.get('/api/home/banner/list')
}
export const getCategoryList = () => {
return request.get('/api/home/category/list')
}
export const getGoodsList = (params: { page: number; size: number }) => {
return request.get('/api/home/goods/list', { params })
}
测试与调试
单元测试
后端测试
bash
# 安装测试依赖
cd php-server
composer require --dev phpunit/phpunit
# 运行测试
./vendor/bin/phpunit tests/
前端测试
bash
# 管理后台测试
cd vue-element-admin
npm run test:unit
# 移动端测试
cd uniapp
npm run test:unit
接口测试
Swagger 文档
bash
# 访问 Swagger 文档
http://localhost:8000/swagger
# 使用 Swagger UI 测试 API
# 支持在线调试和文档查看
Postman 测试
bash
# 导入 Swagger 文档到 Postman
# 创建测试集合
# 设置环境变量
# 运行自动化测试
功能测试
用户功能测试
- 注册登录: 测试用户注册、登录、退出功能
- 个人信息: 测试用户信息修改、密码修改功能
- 地址管理: 测试地址添加、编辑、删除功能
商品功能测试
- 商品浏览: 测试商品列表、详情、搜索功能
- 购物车: 测试商品加入购物车、数量修改功能
- 收藏功能: 测试商品收藏、取消收藏功能
订单功能测试
- 下单流程: 测试商品下单、地址选择、支付功能
- 订单管理: 测试订单查看、取消、确认收货功能
- 退款功能: 测试订单退款申请、处理功能
调试技巧
后端调试
php
// 使用 dd() 函数调试
dd($variable);
// 使用日志记录
\think\facade\Log::info('调试信息', $data);
// 使用数据库查询日志
\think\facade\Db::listen(function ($sql, $time, $explain) {
// 记录 SQL 查询
});
前端调试
javascript
// Vue DevTools 调试
// 浏览器开发者工具
// 网络请求监控
// 控制台日志输出
// 移动端调试
// H5 端使用浏览器开发者工具
// 小程序端使用微信开发者工具
// APP 端使用真机调试
性能优化
后端优化
数据库优化
php
// 使用索引优化查询
// 避免 N+1 查询问题
// 使用分页查询
// 缓存热点数据
// 示例:优化商品列表查询
public function getGoodsList($params)
{
return $this->model
->with(['category', 'store'])
->where('is_enabled', 1)
->order('create_at', 'desc')
->paginate($params['size']);
}
缓存优化
php
// 使用 Redis 缓存
// 设置合理的缓存时间
// 使用缓存标签
// 避免缓存穿透
// 示例:缓存商品信息
public function getGoodsInfo($id)
{
$cache_key = CacheUtil::GOODS_INFO_KEY . $id;
$goods_info = CacheUtil::get($cache_key);
if (!$goods_info) {
$goods_info = $this->model->find($id);
CacheUtil::set($cache_key, $goods_info, 3600, CacheUtil::GOODS_TAG);
}
return $goods_info;
}
前端优化
代码分割
javascript
// 路由懒加载
const routes = [
{
path: '/user',
component: () => import('@/pages/user/index.vue')
}
]
// 组件懒加载
const UserList = defineAsyncComponent(() => import('./UserList.vue'))
图片优化
vue
<!-- 使用懒加载 -->
<image
:src="item.image"
lazy-load
mode="aspectFill"
/>
<!-- 使用 WebP 格式 -->
<image
:src="item.image + '?format=webp'"
mode="aspectFill"
/>
常见问题
开发环境问题
Q: Composer 安装失败
bash
# 解决方案
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
composer install
Q: Node.js 依赖安装失败
bash
# 解决方案
npm config set registry https://registry.npmmirror.com
npm install
Q: 数据库连接失败
bash
# 检查数据库配置
# 确保 MySQL 服务正在运行
# 检查防火墙设置
代码问题
Q: API 接口返回 500 错误
bash
# 检查 PHP 错误日志
tail -f /var/log/php-fpm/error.log
# 检查应用日志
tail -f runtime/log/error.log
# 开启调试模式
APP_DEBUG = true
Q: 前端页面空白
bash
# 检查浏览器控制台错误
# 检查网络请求
# 检查路由配置
# 检查组件导入
性能问题
Q: 页面加载缓慢
bash
# 开启 Gzip 压缩
# 使用 CDN 加速
# 优化图片大小
# 减少 HTTP 请求
Q: 数据库查询慢
bash
# 添加数据库索引
# 优化 SQL 查询
# 使用缓存
# 分页查询
开发工具推荐
IDE 推荐
- VS Code: 免费,插件丰富,支持多语言
- PhpStorm: PHP 开发首选,功能强大
- WebStorm: 前端开发首选,支持 Vue/React
浏览器插件
- Vue DevTools: Vue 开发调试工具
- Redux DevTools: 状态管理调试工具
- Postman: API 接口测试工具
移动端调试
- 微信开发者工具: 小程序开发调试
- HBuilderX: UniApp 开发调试
- Chrome DevTools: H5 页面调试
相关文档
技术文档
- 后端架构 - 后端技术架构详解
- 前端架构(Vue3) - 管理后台架构详解
- 前端架构(UniApp) - 移动端架构详解
- 数据库设计 - 数据库设计详解
开发指南
部署文档
- 快速开始 - 系统安装部署
- ThinkPHP 安装 - 后端安装指南
- Vue3 安装 - 管理后台安装指南
- UniApp 安装 - 移动端安装指南
最后更新:2024-01-20
维护者:DSPlatform技术团队