优化代码

This commit is contained in:
2026-01-04 15:01:16 +08:00
parent e0ff5ccdc7
commit b764c708ce
10 changed files with 2885 additions and 465 deletions

2
.env Normal file
View File

@@ -0,0 +1,2 @@
APP_DEBUG = true
APP_ENV = local

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ unpackage
node_modules
.DS_Store
.hbuilderx
/runtime/

View File

@@ -2,22 +2,16 @@
declare (strict_types=1);
namespace app;
use app\BaseController;
use app\card\model\User;
use app\card\model\UserPhone;
use app\farm\model\Farmer;
use app\restaurant\model\Table;
use app\shop\model\Cap;
use longbingcore\tools\LongbingArr;
use Qiniu\Auth;
use think\App;
use think\exception\HttpResponseException;
use think\exception\ValidateException;
use think\Request;
use think\Validate;
use think\Response;
use think\facade\Db;
use think\facade\Lang;
/**
* 控制器基础类
*/
@@ -86,13 +80,11 @@ abstract class ApiRest extends BaseController
public function __construct(App $app)
{
parent::__construct($app);
if (in_array($this->_method, ['options', 'Options', 'OPTIONS'])) {
echo true;exit;
echo true;
exit;
}
//获取param
@@ -103,36 +95,47 @@ abstract class ApiRest extends BaseController
$this->_header = $this->request->header();
if (is_dev()) {
// 调试代码
$debugUid = $this->_param['debug_uid'] ?? ($this->_input['debug_uid'] ?? null);
if ($debugUid !== null) {
$this->_user_id = $debugUid;
$this->_user = [
'id' => $this->_user_id,
'uniacid' => $this->_uniacid,
'nickname' => 'debug',
];
$this->noNeedLogin[] = $this->request->action();
if (isset($this->_param['debug_uniacid'])) {
$this->_uniacid = $this->_param['debug_uniacid'];
} elseif (isset($this->_input['debug_uniacid'])) {
$this->_uniacid = $this->_input['debug_uniacid'];
}
}
}
$this->is_app = !empty($this->_header['isapp']) ? $this->_header['isapp'] : 0;
if ( defined( 'IS_WEIQIN' ) )
{
if (defined('IS_WEIQIN')) {
global $_GPC, $_W;
$this->_uniacid = $_W['uniacid'];
}
else
{
if(isset($this->_param[ 'i' ]))
{
} else {
if (isset($this->_param['i'])) {
$this->_uniacid = $this->_param['i'];
}
}
if ( defined( 'LONGBING_CARD_UNIACID' ) )
{
if (defined('LONGBING_CARD_UNIACID')) {
define('LONGBING_CARD_UNIACID', $this->_uniacid);
}
$this->shareChangeData($this->_param);
//获取autograph 小程序用户唯一标示
if ( isset( $this->_header[ 'autograph' ] ) && $this->_header[ 'autograph' ])
{
if (isset($this->_header['autograph']) && $this->_header['autograph']) {
$this->autograph = $this->_header['autograph'];
}
else
{
} else {
if (!$this->match($this->noNeedLogin)) {
$this->errorMsg('need login', 401);
@@ -152,7 +155,6 @@ abstract class ApiRest extends BaseController
}
landNotice($this->_uniacid);
}
@@ -161,7 +163,8 @@ abstract class ApiRest extends BaseController
* @DataTime: 2020-08-21 17:43
* @功能说明:
*/
public function shareChangeData($input){
public function shareChangeData($input)
{
$arr = [
@@ -200,6 +203,8 @@ abstract class ApiRest extends BaseController
'farm/app/IndexUser/farmerInfo',
'farm/app/Index/configInfo',
'farm/app/Index/indexStoreList',
];
if (!empty($input['s']) && in_array($input['s'], $arr)) {
@@ -218,18 +223,17 @@ abstract class ApiRest extends BaseController
* @DataTime: 2020-07-09 12:00
* @功能说明:检测方法传递
*/
public function match($arr){
public function match($arr)
{
$arr = is_array($arr) ? $arr : explode(',', $arr);
if (!$arr)
{
if (!$arr) {
return FALSE;
}
$arr = array_map('strtolower', $arr);
// 是否存在
if (in_array(strtolower($this->request->action()), $arr) || in_array('*', $arr))
{
if (in_array(strtolower($this->request->action()), $arr) || in_array('*', $arr)) {
return TRUE;
}
@@ -267,6 +271,7 @@ abstract class ApiRest extends BaseController
$result['code'] = $code;
return $result;
}
/**
* 输出返回数据
* @access protected
@@ -289,26 +294,18 @@ abstract class ApiRest extends BaseController
*/
public function _empty($method)
{
if ( method_exists( $this, $method . '_' . $this->method . '_' . $this->type ) )
{
if (method_exists($this, $method . '_' . $this->method . '_' . $this->type)) {
// RESTFul方法支持
$fun = $method . '_' . $this->method . '_' . $this->type;
}
elseif ( $this->method == $this->restDefaultMethod && method_exists( $this, $method . '_' . $this->type ) )
{
} elseif ($this->method == $this->restDefaultMethod && method_exists($this, $method . '_' . $this->type)) {
$fun = $method . '_' . $this->type;
}
elseif ( $this->type == $this->restDefaultType && method_exists( $this, $method . '_' . $this->method ) )
{
} elseif ($this->type == $this->restDefaultType && method_exists($this, $method . '_' . $this->method)) {
$fun = $method . '_' . $this->method;
}
if ( isset( $fun ) )
{
if (isset($fun)) {
return App::invokeMethod([$this, $fun]
);
}
else
{
} else {
// 抛出异常
throw new \Exception('error action :' . $method);
}
@@ -350,7 +347,8 @@ abstract class ApiRest extends BaseController
* @DataTime: 2021-03-19 15:22
* @功能说明:获取当前的门店信息
*/
public function getStoreInfo($err=1){
public function getStoreInfo($err = 1)
{
$user_id = $this->getUserId();
@@ -389,7 +387,6 @@ abstract class ApiRest extends BaseController
}
/**
* @Purpose: 通过小程序端的用户标示获取用户id
*
@@ -412,8 +409,7 @@ abstract class ApiRest extends BaseController
return $id;
}
if ( ($value === false &&!$this->match($this->noNeedLogin)))
{
if (($value === false && !$this->match($this->noNeedLogin))) {
$this->errorMsg('need login', 401);
@@ -439,14 +435,15 @@ abstract class ApiRest extends BaseController
return !empty($value['id']) ? $value['id'] : 0;
}
/**
*
* 获取支付信息
*/
public function payConfig ($uniacid = '1',$is_app=7){
public function payConfig($uniacid = '1', $is_app = 7)
{
if ($is_app == 7) {
@@ -521,25 +518,22 @@ abstract class ApiRest extends BaseController
->where(['user_id' => $to_uid])
->order('id desc')
->select();
if ( empty( $formId ) )
{
if (empty($formId)) {
return false;
}
if ( $formId[ 0 ][ 'create_time' ] < $beginTime )
{
if ($formId[0]['create_time'] < $beginTime) {
Db::name('longbing_card_formId')
->where(['id' => $formId[0]['id']])
->delete();
$this->getFormId($to_uid);
}
else
{
} else {
Db::name('longbing_card_formId')
->where(['id' => $formId[0]['id']])
->delete();
return $formId[0]['formId'];
}
}
/**
* User: chenniang
* Date: 2019-09-12 20:37
@@ -547,7 +541,8 @@ abstract class ApiRest extends BaseController
* @return void
* descption:直接抛出异常
*/
protected function errorMsg($msg = '',$code = 400){
protected function errorMsg($msg = '', $code = 400)
{
$msg = Lang::get($msg);
$this->results($msg, $code);
}

View File

@@ -1,5 +1,7 @@
<?php
namespace app\farm\controller;
use app\ApiRest;
@@ -21,7 +23,6 @@ use app\farm\model\WelfareColumn;
use app\massage\model\Config;
use app\farm\model\Address;
use app\farm\model\User;
@@ -30,6 +31,9 @@ use app\publics\model\TmplConfig;
use longbingcore\wxcore\YsCloudApi;
use think\App;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\facade\Db;
use think\Request;
@@ -40,7 +44,8 @@ class Index extends ApiRest
protected $model;
public function __construct(App $app) {
public function __construct(App $app)
{
parent::__construct($app);
@@ -49,13 +54,59 @@ class Index extends ApiRest
}
/**
* 农场列表
* @return mixed
* @throws DbException
* @throws ModelNotFoundException
* @throws DataNotFoundException
*/
public function indexStoreList()
{
$dis = [
'uniacid' => $this->_uniacid,
'status' => 2,
'type' => 2,
'business_status' => 1
];
$farmer_model = new \app\farm\model\Farmer();
$lat = !empty($input['lat']) ? $input['lat'] : 0;
$lng = !empty($input['lng']) ? $input['lng'] : 0;
$alh = '(2 * 6378.137* ASIN(SQRT(POW(SIN(PI()*(' . $lng . '- `lng`)/360),2)+COS(PI()*33.07078170776367/180)* COS(' . $lat . ' * PI()/180)*POW(SIN(PI()*(' . $lat . '- lat)/360),2))))*1000 as distance';
//门店列表
$store = $farmer_model->where($dis)->field(['*', $alh])->order('distance,id desc')->limit(10)->select()->toArray();
if (!empty($store)) {
foreach ($store as &$v) {
$v['distance'] = getDistances($v['lng'], $v['lat'], $lng, $lat);
$v['distance'] = $farmer_model->getDistanceAttr($v['distance']);
}
}
return $this->success($store);
}
/**
* @author chenniang
* @DataTime: 2022-03-02 16:58
* @功能说明:首页
*/
public function index(){
public function index()
{
$input = $this->_param;
@@ -174,22 +225,14 @@ class Index extends ApiRest
}
/**
* @author chenniang
* @DataTime: 2022-02-18 16:41
* @功能说明:获取天气
*/
public function weather(){
public function weather()
{
$input = $this->_param;
$dis = [
'uniacid' => $this->_uniacid
@@ -242,10 +285,9 @@ class Index extends ApiRest
* @DataTime: 2021-03-23 14:16
* @功能说明:获取配置信息
*/
public function configInfo(){
public function configInfo()
{
$dis = [
'uniacid' => $this->_uniacid
];
@@ -258,13 +300,13 @@ class Index extends ApiRest
}
/**
* @author chenniang
* @DataTime: 2021-12-30 15:21
* @功能说明:
*/
public function farmerSelectList(){
public function farmerSelectList()
{
$dis = [
@@ -289,7 +331,8 @@ class Index extends ApiRest
* @DataTime: 2022-01-07 14:35
* @功能说明:农场列表
*/
public function farmerList(){
public function farmerList()
{
$input = $this->_param;
@@ -368,7 +411,8 @@ class Index extends ApiRest
* @功能说明:农场详情
*
*/
public function farmerInfo(){
public function farmerInfo()
{
$input = $this->_param;
@@ -404,7 +448,8 @@ class Index extends ApiRest
* @DataTime: 2022-01-06 15:21
* @功能说明:文章详情
*/
public function articleInfo(){
public function articleInfo()
{
$input = $this->_param;
@@ -425,7 +470,8 @@ class Index extends ApiRest
* @DataTime: 2021-12-30 11:51
* @功能说明:关于我们列表
*/
public function aboutUsList(){
public function aboutUsList()
{
$input = $this->_param;
@@ -454,7 +500,8 @@ class Index extends ApiRest
* @DataTime: 2021-11-08 13:23
* @功能说明:我们列表详情
*/
public function aboutUsInfoType(){
public function aboutUsInfoType()
{
$input = $this->_param;
@@ -491,7 +538,8 @@ class Index extends ApiRest
* @DataTime: 2022-02-09 17:07
* @功能说明:添加评价
*/
public function evaluateAdd(){
public function evaluateAdd()
{
$input = $this->_input;
@@ -547,7 +595,8 @@ class Index extends ApiRest
* @DataTime: 2022-02-09 17:17
* @功能说明:用户评列表
*/
public function userEvaluateList(){
public function userEvaluateList()
{
$input = $this->_param;
@@ -591,7 +640,8 @@ class Index extends ApiRest
* @DataTime: 2022-02-09 17:21
* @功能说明:评价详情
*/
public function evaluateInfo(){
public function evaluateInfo()
{
$input = $this->_param;
@@ -614,7 +664,8 @@ class Index extends ApiRest
* @DataTime: 2022-02-09 17:21
* @功能说明:评价详情
*/
public function evaluateUpdate(){
public function evaluateUpdate()
{
$input = $this->_input;
@@ -644,7 +695,8 @@ class Index extends ApiRest
* @DataTime: 2022-02-10 17:23
* @功能说明:待评价订单
*/
public function noEvaluateOrderList(){
public function noEvaluateOrderList()
{
$input = $this->_param;
@@ -705,7 +757,8 @@ class Index extends ApiRest
* @DataTime: 2022-02-11 10:11
* @功能说明:根据type获取对应物品的评价
*/
public function goodsEvaluateList(){
public function goodsEvaluateList()
{
$input = $this->_param;
@@ -723,7 +776,8 @@ class Index extends ApiRest
* @DataTime: 2022-03-09 16:50
* @功能说明:模版列表
*/
public function tmpList(){
public function tmpList()
{
//模版消息model
$tmpl_model = new TmplConfig();
//获取模版
@@ -739,7 +793,8 @@ class Index extends ApiRest
* @DataTime: 2022-04-11 15:56
* @功能说明:监控列表
*/
public function monitorList(){
public function monitorList()
{
$input = $this->_param;
@@ -795,7 +850,8 @@ class Index extends ApiRest
* @DataTime: 2022-04-11 16:31
* @功能说明:获取监控地址
*/
public function getMonitorInfo(){
public function getMonitorInfo()
{
$input = $this->_param;
@@ -817,7 +873,8 @@ class Index extends ApiRest
* @DataTime: 2022-04-12 11:01
* @功能说明:获取萤石云token
*/
public function getYsToken(){
public function getYsToken()
{
$api = new YsCloudApi($this->_uniacid);
@@ -833,7 +890,8 @@ class Index extends ApiRest
* @DataTime: 2022-04-12 11:02
* @功能说明:旋转摄像头
*/
public function ysStartTurn(){
public function ysStartTurn()
{
$input = $this->_param;
@@ -850,13 +908,13 @@ class Index extends ApiRest
}
/**
* @author chenniang
* @DataTime: 2022-04-12 11:02
* @功能说明:停止旋转摄像头
*/
public function ysStopTurn(){
public function ysStopTurn()
{
$input = $this->_param;
@@ -875,7 +933,6 @@ class Index extends ApiRest
}
public function haveStore()
{
@@ -895,7 +952,8 @@ class Index extends ApiRest
* @DataTime: 2022-07-22 17:34
* @功能说明:公益列表
*/
public function welfareColumnList(){
public function welfareColumnList()
{
$input = $this->_param;
@@ -926,7 +984,8 @@ class Index extends ApiRest
* @DataTime: 2022-07-22 17:34
* @功能说明:公益列表
*/
public function welfareColumnInfo(){
public function welfareColumnInfo()
{
$input = $this->_param;
@@ -946,14 +1005,13 @@ class Index extends ApiRest
}
/**
* @author chenniang
* @DataTime: 2022-06-15 16:29
* @功能说明:优惠券
*/
public function couponList(){
public function couponList()
{
$coupon_record_model = new CouponRecord();
@@ -1007,7 +1065,8 @@ class Index extends ApiRest
* @DataTime: 2022-06-15 22:49
* @功能说明:用户获取卡券
*/
public function userGetCoupon(){
public function userGetCoupon()
{
$input = $this->_input;
@@ -1024,6 +1083,4 @@ class Index extends ApiRest
}
}

View File

@@ -1,5 +1,7 @@
<?php
namespace app\farm\controller;
use app\ApiRest;
use app\farm\model\Farmer;
@@ -24,7 +26,6 @@ use think\App;
use think\Request;
class IndexGoods extends ApiRest
{
@@ -34,7 +35,8 @@ class IndexGoods extends ApiRest
protected $car_model;
public function __construct(App $app) {
public function __construct(App $app)
{
parent::__construct($app);
@@ -47,38 +49,29 @@ class IndexGoods extends ApiRest
}
/**
* @author chenniang
* @DataTime: 2021-03-19 11:57
* @功能说明:首页选择店铺列表
*/
public function indexStoreList(){
public function indexStoreList()
{
$input = $this->_param;
$page = $input['page'] ?? 10;
$dis[] = ['uniacid', '=', $this->_uniacid];
$dis[] = ['status', '=', 2];
$dis[] = ['business_status', '=', 1];
if (!empty($input['store_name'])) {
$dis[] = ['title', 'like', '%' . $input['store_name'] . '%'];
}
$lat = !empty($input['lat']) ? $input['lat'] : 0;
$lng = !empty($input['lng']) ? $input['lng'] : 0;
$alh = '(2 * 6378.137* ASIN(SQRT(POW(SIN(3.1415926535898*(' . $lat . '-lat)/360),2)+COS(3.1415926535898*' . $lat . '/180)* COS(' . $lat . ' * 3.1415926535898/180)*POW(SIN(3.1415926535898*(' . $lng . '-lng)/360),2))))*1000 as distance';
$alhs = '(2 * 6378.137* ASIN(SQRT(POW(SIN(3.1415926535898*(' . $lat . '-lat)/360),2)+COS(3.1415926535898*' . $lat . '/180)* COS(' . $lat . ' * 3.1415926535898/180)*POW(SIN(3.1415926535898*(' . $lng . '-lng)/360),2))))*1000<20000';
$store_model = new Farmer();
$data = $store_model->dataDistanceList($dis,$alh,$alhs,10);
$data = $store_model->dataDistanceList($dis, $alh, $alhs, $page);
if (!empty($data['data'])) {
@@ -93,17 +86,16 @@ class IndexGoods extends ApiRest
}
return $this->success($data);
}
/**
* @author chenniang
* @DataTime: 2021-03-19 13:24
* @功能说明:选择门店
*/
public function selectStore(){
public function selectStore()
{
$input = $this->_input;
@@ -121,7 +113,8 @@ class IndexGoods extends ApiRest
* @DataTime: 2021-03-18 16:46
* @功能说明:分类列表
*/
public function goodsIndex(){
public function goodsIndex()
{
$input = $this->_param;
@@ -146,7 +139,8 @@ class IndexGoods extends ApiRest
* @DataTime: 2022-03-01 13:47
* @功能说明:
*/
public function haveSelectStoreId(){
public function haveSelectStoreId()
{
return $this->success($this->getStoreInfo()['id']);
@@ -158,7 +152,8 @@ class IndexGoods extends ApiRest
* @DataTime: 2022-02-22 15:27
* @功能说明:商品列表
*/
public function goodsList(){
public function goodsList()
{
$input = $this->_param;
@@ -213,13 +208,13 @@ class IndexGoods extends ApiRest
}
/**
* @author chenniang
* @DataTime: 2021-03-19 15:46
* @功能说明:商品详情
*/
public function goodsInfo(){
public function goodsInfo()
{
$input = $this->_param;
@@ -281,14 +276,13 @@ class IndexGoods extends ApiRest
}
/**
* @author chenniang
* @DataTime: 2021-03-24 14:07
* @功能说明:购物车信息
*/
public function carInfo(){
public function carInfo()
{
$input = $this->_param;
@@ -301,14 +295,13 @@ class IndexGoods extends ApiRest
}
/**
* @author chenniang
* @DataTime: 2021-03-24 14:46
* @功能说明:添加到购物车
*/
public function addCar(){
public function addCar()
{
$input = $this->_input;
@@ -365,7 +358,8 @@ class IndexGoods extends ApiRest
* @DataTime: 2021-03-24 14:54
* @功能说明:删除购物车
*/
public function delCar(){
public function delCar()
{
$input = $this->_input;
@@ -392,7 +386,8 @@ class IndexGoods extends ApiRest
* @DataTime: 2021-03-25 10:39
* @功能说明:
*/
public function carUpdate(){
public function carUpdate()
{
$input = $this->_input;
@@ -415,7 +410,8 @@ class IndexGoods extends ApiRest
* @DataTime: 2021-03-24 14:59
* @功能说明:批量删除购物车
*/
public function delSomeCar(){
public function delSomeCar()
{
$input = $this->_input;

View File

@@ -497,6 +497,8 @@ Route::group('app', function () {
Route::get('Index/aboutUsList', 'Index/aboutUsList');
//获取配置信息
Route::get('Index/configInfo', 'Index/configInfo');
// 农场
Route::get('Index/indexStoreList', 'Index/indexStoreList');
//文章详情(id)
Route::get('Index/articleInfo', 'Index/articleInfo');
//模版列表

View File

@@ -1,4 +1,5 @@
<?php
namespace app\massage\model;
use app\BaseModel;
@@ -16,7 +17,8 @@ class Config extends BaseModel
* @DataTime: 2020-09-29 11:04
* @功能说明:添加
*/
public function dataAdd($data){
public function dataAdd($data)
{
$res = $this->insert($data);
@@ -25,13 +27,13 @@ class Config extends BaseModel
}
/**
* @author chenniang
* @DataTime: 2020-09-29 11:05
* @功能说明:编辑
*/
public function dataUpdate($dis,$data){
public function dataUpdate($dis, $data)
{
$res = $this->where($dis)->update($data);
@@ -45,12 +47,10 @@ class Config extends BaseModel
* @DataTime: 2020-09-29 11:06
* @功能说明:列表
*/
public function dataList($dis,$page){
public function dataList($dis, $page)
{
$data = $this->where($dis)->order('id desc')->paginate($page)->toArray();
return $data;
}
@@ -59,26 +59,16 @@ class Config extends BaseModel
* @DataTime: 2020-09-29 11:43
* @功能说明:
*/
public function dataInfo($dis){
public function dataInfo($dis)
{
$data = $this->where($dis)->find();
if (empty($data)) {
$this->dataAdd($dis);
$data = $this->where($dis)->find();
}
return !empty($data) ? $data->toArray() : [];
}
}

View File

@@ -0,0 +1,196 @@
# 后端架构深度解析与开发指南
## 1. 引言
本文档旨在为开发人员提供一份关于本项目(智慧农场系统)后端架构的深度技术解析。不同于基础的架构介绍,本文档聚焦于**代码实现细节**、**数据库设计范式**以及**核心业务流程**的底层逻辑。它将帮助你在接手项目后快速理解现有代码模式Patterns并安全、高效地扩展新功能。
---
## 2. 核心架构与技术实现
本项目基于 **ThinkPHP 6** 框架开发,采用了典型的 **MVC** 架构并结合了多应用Multi-app模式。
### 2.1 C层Controller技术实现
控制器层主要负责请求接收、参数校验和业务调度。项目在控制器层有以下显著的实现模式:
#### 2.1.1 基类封装 (`app/ApiRest.php`)
所有 API 控制器均继承自 `app\ApiRest`,该基类提供了以下核心能力:
- **统一响应格式**:通过 `success($data)``error($msg)` 方法,强制统一 API 返回结构:
```json
{
"code": 200,
"data": { ... },
"sign": "..." // 签名(可选)
}
```
- **多租户隔离**:在构造函数中自动解析 `uniacid`(商户/租户ID并赋值给 `$this->_uniacid`。这是系统支持多商户SaaS模式的核心。
- **鉴权机制**
- 检查 HTTP Header 中的 `autograph`(小程序用户标识)。
- 维护一个 `$noNeedLogin` 白名单。
- **注意**`shareChangeData` 方法中硬编码了大量白名单路由,这种实现方式维护成本较高,建议后续改为中间件或注解配置。
#### 2.1.2 胖模型,瘦控制器
控制器(如 `app/farm/controller/IndexLand.php`)通常比较轻量,不包含复杂的计算逻辑。
- **示例**:在“土地下单”流程中,价格计算、优惠券抵扣等逻辑全部下沉到了 Model 层 (`LandOrder::payOrderInfo`)。
- **优点**:业务逻辑可复用,控制器代码整洁。
### 2.2 M层Model技术实现
模型层不仅负责数据库交互,还承担了大部分业务逻辑。
#### 2.2.1 基类扩展 (`app/BaseModel.php`)
- 实现了基础的 CRUD 方法(`getRow`, `listRow`, `createRow` 等)。
- **软删除机制**:手动实现了基于 `deleted` 字段和 `delete_time` 的软删除,未使用 TP 自带的 `SoftDelete` Trait。
- **代码现状**:核心业务 Model如 `LandOrder`)往往重写了 `dataAdd`/`dataUpdate` 方法,直接调用 TP 的 `insert`/`update`,并未完全依赖 `BaseModel` 的封装。这导致项目中存在两种 CRUD 风格。
#### 2.2.2 高级特性应用
- **获取器 (Getters)**:大量使用 TP 的获取器处理字段格式化。例如 `LandList::getImgsAttr` 将数据库存储的逗号分隔字符串自动转换为数组。
- **动态追加字段**:使用 `$append` 属性在 JSON 序列化时追加计算字段(如 `min_price`)。
- **观察者模式**:在 `LandList::updateSome` 中,手动实现了观察者模式 (`app\farm\server\Land`) 来同步更新关联表数据。这是项目中少见的设计模式应用,值得保留和参考。
---
## 3. 数据库深度解析
### 3.1 数据库概览
- **引擎**:大部分表使用 `MyISAM`。
- **风险**MyISAM 不支持事务,且在并发写入时是表级锁。对于涉及资金交易的表(如 `balance_water`, `land_order`),强烈建议迁移到 `InnoDB`。
- **字符集**`utf8` / `utf8mb4`。
- **多租户设计**:几乎所有表都包含 `uniacid` 字段,用于物理隔离不同商户的数据。
### 3.2 核心业务实体关系 (ER图)
以下是“土地认养”核心业务的实体关系推导:
```mermaid
erDiagram
User ||--o{ LandOrder : "下单"
User ||--o{ Address : "拥有"
Farmer ||--o{ LandList : "管理"
LandList ||--|{ LandSpe : "包含规格"
LandList ||--o{ LandMassif : "包含地块"
LandOrder }|--|| LandList : "关联土地"
LandOrder }|--|| LandSpe : "关联规格"
LandOrder }|--|| LandMassif : "关联地块"
LandOrder ||--|{ LandOrderSeed : "包含种子"
User {
int id PK
int uniacid
string openid
string nickName
}
LandList {
int id PK
int farmer_id FK "农场主"
string title "土地名称"
int status
}
LandOrder {
int id PK
string order_code "订单号"
decimal pay_price "支付金额"
int pay_type "1:未支付 2:已支付"
int status
}
```
### 3.3 关键表结构解析
#### 1. `ims_lbfarm_land_list` (土地表)
- 核心商品表,存储土地的基本信息。
- 关联表:`ims_lbfarm_land_spe` (规格), `ims_lbfarm_land_massif` (地块/位置)。
#### 2. `ims_lbfarm_land_order` (土地订单表)
- **核心字段**
- `order_code`: 业务订单号。
- `transaction_id`: 微信支付流水号。
- `pay_type`: 支付状态 (1=未支付, 2=微信支付, 3=余额支付)。
- `seed_data`: 种子信息的 JSON 快照(反范式设计,避免关联查询)。
---
## 4. 核心业务流程剖析
### 4.1 土地认养下单与支付流程
这是一个跨越 Controller、Model 和 Service 的复杂流程:
1. **预下单 (计价)**
- **入口**`IndexLand::landPayOrderInfo`
- **逻辑**:调用 `LandOrder::payOrderInfo`。
- **计算公式**`总价 = 土地规格单价 + (地块单价 * 认养周期) + ∑(种子单价 * 数量) - 优惠券金额`。
2. **创建订单**
- **入口**`IndexLand::landPayOrder`
- **逻辑**:再次调用 `payOrderInfo` 校验价格,然后写入 `land_order` 表,同时写入 `land_order_seed` 表。
3. **支付回调 (核心)**
- **逻辑位置**`LandOrder::orderResult`
- **事务处理**
1. 开启事务 (`Db::startTrans()`)。
2. 更新订单状态 (`pay_type=2`, `pay_time`)。
3. **余额扣除**:如果使用了余额抵扣,调用 `BalanceWater::addWater`。
4. **资金流水**:记录 `FinanceWater`。
5. **分销结算**:调用 `DistributionCash::addUserCash` 计算分销佣金。
6. 提交事务 (`Db::commit()`)。
- **后续动作**:发送微信订阅消息 (`paySendService`),这是异步或非事务性的操作,放在 commit 之后执行是正确的。
---
## 5. 开发实战指南
### 5.1 如何新增一个 API 接口?
假设你需要新增一个“获取最近的农场列表”接口:
1. **创建控制器**
在 `app/farm/controller` 下创建 `IndexFarm.php`,继承 `ApiRest`。
```php
class IndexFarm extends ApiRest {
public function nearby() {
$lat = $this->_param['lat'];
$lng = $this->_param['lng'];
// 调用 Model 获取数据
$data = (new Farmer())->getNearby($lat, $lng);
return $this->success($data);
}
}
```
2. **实现 Model 逻辑**
在 `app/farm/model/Farmer.php` 中实现 `getNearby` 方法。建议参考 `LandList::indexDataList` 中的距离计算 SQL。
3. **配置路由**
在 `app/farm/route/route.php` 中注册路由(如果项目未开启自动路由)。目前看项目启用了自动路由,直接访问 `farm/index_farm/nearby` 即可。
4. **配置鉴权**
如果该接口不需要登录,需要在 `ApiRest::shareChangeData` 或控制器的构造函数中将其加入 `$noNeedLogin` 数组。
### 5.2 常见坑与规避
1. **数据库事务**
由于大量表使用 `MyISAM`,在涉及多表更新时(如订单状态+库存扣减),事务可能不会生效。
- **建议**:在开发新功能涉及核心交易表时,务必检查表引擎,必要时手动 `ALTER TABLE ... ENGINE=InnoDB`。
2. **参数获取**
尽量使用 `$this->_param` 获取参数,它合并了 GET 和 POST 数据。避免直接使用 `$_GET` 或 `$_POST`。
3. **浮点数计算**
项目中存在直接使用浮点数计算金额的情况(如 `round($price, 2)`)。在涉及金额比对时,建议使用 `bccomp` 等高精度函数,避免精度丢失导致的 0.01 元误差。
4. **SQL 注入风险**
虽然 TP 的 ORM 有防注入机制,但在拼接原生 SQL如距离计算务必使用参数绑定严禁直接拼接变量。
---
## 6. 附录:推荐的重构方向
1. **引入 .env 配置**:将 `config/database.php` 中的硬编码配置移至环境变量。
2. **统一 Model 规范**:废弃 `dataAdd` 等自定义方法,统一使用 TP 标准的 `save/create`,或完善 `BaseModel` 并强制继承。
3. **中间件鉴权**:将 `ApiRest` 中的硬编码鉴权逻辑重构为 TP 中间件。
4. **数据库迁移**:编写脚本将所有 `MyISAM` 表迁移至 `InnoDB`。

2138
doc/stsm_farm.sql Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -202,6 +202,34 @@ if (!function_exists('dump')) {
}
}
if (!function_exists('dd')) {
/**
* 浏览器友好的变量输出
* @param mixed $vars 要输出的变量
* @return void
*/
function dd(...$vars)
{
ob_start();
var_dump(...$vars);
$output = ob_get_clean();
$output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output);
if (PHP_SAPI == 'cli') {
$output = PHP_EOL . $output . PHP_EOL;
} else {
if (!extension_loaded('xdebug')) {
$output = htmlspecialchars($output, ENT_SUBSTITUTE);
}
$output = '<pre>' . $output . '</pre>';
}
echo $output;
die;
}
}
if (!function_exists('env')) {
/**
* 获取环境变量值
@@ -661,3 +689,18 @@ if (!function_exists('root_path')) {
return app()->getRootPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path);
}
}
if (!function_exists('is_dev')) {
function is_dev()
{
$env = env('APP_ENV', '');
$env = strtolower($env);
if (in_array($env, ['dev', 'development', 'local', 'test'], true)) {
return true;
}
if (app()->environment('dev') || app()->environment('local') || app()->environment('test')) {
return true;
}
return false;
}
}