使用tinkphp6.1带你一步一步开发一个商城管理系统<后台会员模块>

后台会员的数据来源一般都是用户在网站进行注册而得到的,因此在讲后台管理模块时,会穿插讲一下会员的注册
源码地址:https://gitee.com/myha/demo-shop

3.1功能简介

下面规划的一些基础功能,前期从简单的开始,后面再来丰富它的功能

功能描述
会员注册手机号注册、手机验证码
会员信息查询、更新、启用/禁用、删除
会员等级新增、查询、更新、删除

3.2手机验证码

目前用户注册比较主流的都是使用手机号了,因此我们先来实现一下发生手机验证码的功能

3.2.1免费的短信服务

手机短信平台有很多,例如百度的、腾讯的、阿里的,无论哪个平台都有相应的sdk,这里我们使用阿里云的短信服务,上面提供了免费服务供我们学习测试打开如下

地址:https://dysms.console.aliyun.com/quickstart,然后获取相对应的签名和模板

在这里插入图片描述
在这里插入图片描述

这里边是示例代码,有几个比较重要的参数,accessKeyIdaccessKeySecretsignNametemplateCode

其中signNametemplateCode示例代码有

至于accessKeyIdaccessKeySecretd的获取,从下面入口进入即可

在这里插入图片描述

做完这些准备工作后,下面开始写代码了

3.2.2策略模式

这里我们学习一种php的设计模式-策略模式,来封装发送验证码的核心代码,我们先了解一下它的概念

策略模式又叫做政策模式,用于如何组织和调用算法的,是属于行为型模式的一种。 策略模式需要三个角色构成:

  • Context 封装角色:也叫做上下文角色,起承上启下封装作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化
  • Strategy 抽象策略角色:通常为接口,指定规则
  • ConcreteStrategy 具体策略角色:实现抽象策略中的操作,该类含有具体的算法

优点

算法可以通过参数自由切换, 方便扩展,增加策略只需要实现接口就行了 。

第一步:Strategy 抽象策略角色

它就是定义一个基础接口,这样做的目的就是规范代码,不管你用哪个服务商的代码,但都必须实现这个基础接口

commom应用里面新建一个lib目录(第三方组件)—>msg目录(手机发送信息组件)—>Message.php

namespace app\common\lib\msg;

interface Message
{   
    /**
     * 发送验证码
     * @param  string     $mobile     手机号
     * @param  string     $code       验证码
     * @return bool
     */
    public function send($mobile,$code);
}

这里定义了一个send()方法,后续接入进来的服务商代码都要继承这个接口,实现send()方法

**第二步:ConcreteStrategy 具体策略角色 **

以阿里云的服务商为例,我们把代码复制过来

class AliYunMessage implements Message
{	//实现接口
    public function send($mobile, $code){
        //读取配置文件的配置
        $config = new Config([
            "accessKeyId" => config('aliyun.accessKeyId'),
            "accessKeySecret" => config('aliyun.accessKeySecret')
        ]);
        $config->endpoint = config('aliyun.dysmsapi');
        $client = new Dysmsapi($config);

        $sendSmsRequest = new SendSmsRequest([
            "phoneNumbers" => $mobile,
            "signName" => config('aliyun.signName'),
            "templateCode" => config('aliyun.templateCode'),
            "templateParam" => json_encode(['code'=>$code])
        ]);

        $runtime = new RuntimeOptions([]);
        try {
            // 复制代码运行请自行打印 API 的返回值
            $rs = $client->sendSmsWithOptions($sendSmsRequest, $runtime);
            Log::write("获取手机验证码:".json_encode($rs));
            return true;
        }
        catch (Exception $error) {
            if (!($error instanceof TeaError)) {
                $error = new TeaError([], $error->getMessage(), $error->getCode(), $error);
            }
            Log::write('获取手机验证码异常:'.$error->message);
            return false;
        }
    }
}

代码中第一步就是读取配置文件的一些关键信息,这里需要common下新建config目录新建aliyun.php,内容如下

<?php
// +----------------------------------------------------------------------
// | 阿里云相关配置
// +----------------------------------------------------------------------

return [
    //AccessKey ID
    'accessKeyId'         => '',
    // AccessKey Secret
    'accessKeySecret'    => '',
    
    /***短信配置****/
    // 签名
    'signName'       => '阿里云短信测试',
    // 短信模板CODE
    'templateCode'      => 'SMS_154950909',
    // 访问的域名
    'dysmsapi' => 'dysmsapi.aliyuncs.com'
];

第三步:Context 封装角色

namespace app\common\lib\msg;

class MessageContext
{
    private $message;
	
    public function __construct(Message $msg)
    {
        $this->message = $msg;
    }

    public function sendMessage($mobile,$code)
    {
        return $this->message->send($mobile,$code);
    }
}

这个类里面的构造函数的参数实际就是策略类,然后再调用其里面的send()方法

最后看看调用示例

$msgCtx = new MessageContext(new AliYunMessage);
$msgCtx->sendMessage($mobile,$code);

有些人可能会觉得,如果直接在AliYunMessage类定义一个静态方法,然后直接调用是不是更加方便?

显然,确实是更加方便。

这里我们使用这种策略模式,其目的就是多学习一下php的设计模式,它本身的代码也不复杂,适合在项目中使用。另外它的一个优点就是调用方并不是直接调用算法,而是通过上下文角色进行调用,这样我们就不需要关注算法本身,如果我们要加策略,直接新增类就行。

注:

把手机验证码发送的核心逻辑封装在common应用里面,主要还是因为它是一个公用的功能,任务其它应用如果使用到手机验证码都可以调用它

3.3用户注册

3.3.1获取手机验证码

用户注册肯定是web端应用,因此在获取验证码的之前,我们先建一个pc目录作为web端应用,在pc目录下新建如下目录

├─app           应用目录
│  ├─pc           应用目录
│  │  ├─controller      控制器目录
│  │  ├─config          配置目录
│  │  ├─route           路由目录
│  │  ├─service         业务核心目录
│  │  ├─middleware      中间件目录
│  │  └─ ...            更多类库目录

类似于admin应用,我们也为pc应用新建一个PcController的基础控制器,内容跟admin应用差不多,大家可以下载源码看

接下来在common应用下的service目录,新建UserService.php用户核心业务层

/**
 * 获取手机验证码
 * @param  string  $mobile  手机号
 * @param  string  $limit   位数
 * @return bool
*/
public static function getMobileCode($mobile, $limit = 4){
    if($limit == 4){
        $code = rand(0000,9999);
    }else{
        $code = rand(000000,999999);
    }
    
    //如果手机验证码还没有过期,则提示不要重复发送
    if(cache(config('cachekey.mobile_code').$mobile)){
        serviceException(config('error.er3')['code'],config('error.er3')['msg']);
    }

    $msgCtx = new MessageContext(new AliYunMessage);
    if($msgCtx->sendMessage($mobile,$code)){
        cache(config('cachekey.mobile_code').$mobile,$code,120);
        return true;
    }else{
        return false;
    }
}

该函数实现了手机发送4位或6位手机验证码,这里调用我们前面封装好的函数,然后保存到缓存中,注意这里的key值,一定要把手机号作为key的一部分,这样才能唯一,同时缓存时间设置位120秒

3.3.2注册

在common/model新建UserModel.php

<?php
// +----------------------------------------------------------------------
// | 用户模型
// +----------------------------------------------------------------------
// | Author: myh
// +----------------------------------------------------------------------
namespace app\common\model;

use think\Model;

class UserModel extends BaseModel
{   
    protected $table = 'ds_user';
}

pc应用下的service目录新建PuserService.php

/**
 * 用户注册
 * @param  array  $data  新增的数据
 * @return int
*/
public static function register($data){
    //验证该账号是否存在
    if(UserModel::getByAccount($data['account'])){
        serviceException(config('error.er8')['code'],config('error.er8')['msg']);
    }

    //验证手机号是否存在
    if(UserModel::getByMobile($data['mobile'])){
        serviceException(config('error.er9')['code'],config('error.er9')['msg']);
    }

    $data['password'] = createPassword($data['password']);
    $user = new UserModel;
    $user->save($data);
    cache(config('cachekey.mobile_code').$data['mobile'],null);
    return $user->id;
}

首先判断账号和手机号是否被注册,然后对密码进行加密存储,最后新增到数表中,并删除验证码缓存。

这里的密码加密函数,是自定义在app目录下的common.php

/**
 * 密码加密
 * @param string $pw       要加密的原始密码
 * @param string $authCode 加密字符串
 * @return string
 */
function createPassword($pw, $authCode = '')
{
    if (empty($authCode)) {
        $authCode = config('app.authcode');
    }
    $result = "***" . md5(md5($authCode . $pw));
    return $result;
}


/**
 * 密码比较方法,所有涉及密码比较的地方都用这个方法
 * @param string $password     要比较的密码
 * @param string $passwordInDb 数据库保存的已经加密过的密码
 * @return boolean 密码相同,返回true
 */
function comparePassword($password, $passwordInDb)
{
    return createPassword($password) == $passwordInDb;
}

一个是加密函数,一个是比较函数

最后就是控制器User.php代码

//用户注册
public function register(){
    $data = $this->request->post();
    $data['account'] = trim($data['account']);
    //验证规则
    $validate = [
        'account'  => 'require',
        'password'  => 'require|regex:/^[a-zA-Z0-9_]{8,20}$/',
        'repassword'  => 'require',
        'mobile'  => 'require|regex:/^1[3-9]\d{9}$/',
        'code'  => 'require'
    ];
    //提示信息
    $message = [
        'account.require' => '账号不能为空!',
        'password.require' => '密码不能为空!',
        'password.regex' => '密码长度8~20位,包含字母数字下划线!',
        'repassword.require' => '确认密码不能为空!',
        'mobile.require' => '手机号不能为空!',
        'mobile.regex' => '手机号格式不正确!',
        'code.require' => '验证码不能为空!',
    ];

    $this->validate($data, $validate, $message);

    //验证码不一致
    if($data['code'] != config('cachekey.mobile_code').$data['mobile']){
        return failure(config('error.er7')['code'],config('error.er7')['msg']);
    }

    //验证两次输入的密码是否一致
    if($data['password'] != $data['repassword']){
        return failure(config('error.er6')['code'],config('error.er6')['msg']);
    }

    if(PuserService::register($data)){
        return success();
    }else{
        return failure();
    }
}

没有啥逻辑可言,就是验证数据

最后就是路由

//用户注册
Route::post('user/register','user/register');

3.4用户列表

这一章回到我们的admin应用,管理员可以查看会员的信息,包括对其进行禁用/启用,首先我们实现它的列表接口

打开UserService.php,新增如下内容

/**
 * 用户列表
 * @param  array  $param  请求参数
 * @return array
*/
public static function page($param){
    $data = UserModel::page($param,['withoutField'=>'password']);

    foreach ($data['list']  as $k => $v) {
        $data['list'][$k]['status_text'] = UserModel::$status[$v['status']];
    }

    return $data;
}

就是这么方便,我们直接调用BaseModel里面封装的page()方法即可,获取到数据后,我们需要对一些字段进行转换,例如代码中的状态,需要把数字转换成中文,因此在UserModel模型定义了如下的静态数组

public static $status = [0=>'禁用',1=>'正常'];

控制器代码更简单了,在admin/controller应用下新建User.php

//分页列表
public function page(){
    return $this->success(UserService::page($this->request->get()));
}

注:

只要前期做好了代码的封装,后续开发会非常的方便,包括后面要做的会员等级列表也是一样

3.5启用/禁用

可能因为某些原因,我们需要对违规的会员账号进行禁用操作,目前实现这一功能主要是更改会员表的status字段即可

打开common/service/UserService.php

/**
 * 更新数据
 * @param  array  $data  更新数据
 * @return bool
*/
public static function update($data){
    $user = UserModel::find($data['id']);
    if(!$user){
        serviceException(config('error.er15')['code'],config('error.er15')['msg']);
    }

    if($user->save($data)){
        return true;
    }else{
        return false;
    }
}

启用、禁用本质上是修改数据,因此我们在common应用下封装一个更新用户信息的方法,后面会员修改个人信息的时候也会用到

再看看控制器User.php代码

//启用、禁用
public function updateStatus(){
    $data = $this->request->post();
    //验证规则
    $validate = [
        'id'  => 'require',
        'status'  => 'require'
    ];
    //提示信息
    $message = [
        'id.require' => '请选择要禁用的数据!',
        'status.require' => '状态不能为空!',
    ];

    $this->validate($data, $validate, $message);

    if(UserService::update($data)){
        return success();
    }else{
        return failure();
    }
}

3.6会员等级

会员等级的管理并没有什么复杂的逻辑,只是简单的对数据的增删改查,后面如果遇到跟它类似的模块都按照这个模块的方式去开发

新建common/model/UserLevelModel.php

<?php
// +----------------------------------------------------------------------
// | 用户等级模型
// +----------------------------------------------------------------------
// | Author: myh
// +----------------------------------------------------------------------
namespace app\common\model;

class UserLevelModel extends BaseModel
{   
    protected $table = 'ds_user_level';
}

新建common/model/UserLevelService.php

/**
 * 新增等级数据
 * @param  array  $data  新增的数据
 * @return int
*/
public static function save($data){
    $userLevel = new UserLevelModel;
    $userLevel->save($data);
    //清空缓存
    cache(config('redisKey.USER_LEVEL_LIST'),null);
    return $userLevel->id;
}

/**
 * 更新等级数据
 * @param  array  $data 更新的数据
 * @return int
*/
public static function update($data){
    $user = UserLevelModel::find($data['id']);
    if(!$user){
        serviceException(config('error.er15')['code'],config('error.er15')['msg']);
    }

    if($user->save($data)){
        //清空缓存
        cache(config('redisKey.USER_LEVEL_LIST'),null);
        return true;
    }else{
        return false;
    }
}

/**
 * 删除
 * @param  string  $ids  需要删除数据的id
*/
public static function destroy($ids){
    if(!empty($ids)){
        //清空缓存
        cache(config('redisKey.USER_LEVEL_LIST'),null);
        UserLevelModel::destroy(explode(',',$ids));
    }
}

/**
 * 会员等级列表
 * @return array
*/
public static function list(){
    if(cache(config('redisKey.USER_LEVEL_LIST'))){
        return cache(config('redisKey.USER_LEVEL_LIST'));
    }

    $list = UserLevelModel::order('growth_value','asc')->select();
    if(!empty($list)){
        //永久缓存
        cache(config('redisKey.USER_LEVEL_LIST'),$list,0);
    }

    return $list;
}

会员等级不会有很多条数据,因此这里不做分页,同时我们给列表数据加上缓存。

控制器UserLevel.php代码

<?php
// +----------------------------------------------------------------------
// | 用户等级模块
// +----------------------------------------------------------------------
// | Author: myh
// +----------------------------------------------------------------------
namespace app\admin\controller;

use app\common\service\UserLevelService;

class UserLevel extends AdminController
{
    //列表
    public function list(){
        $param = $this->request->get();
        return $this->success(UserLevelService::list($param));
    }

    //新增
    public function add(){
        $data = $this->request->post();
        //验证规则
        $validate = [
            'level_name'  => 'require',
            'growth_value'  => 'require',
        ];
        //提示信息
        $message = [
            'level_name.require' => '等级名称不能为空!',
            'growth_value.require' => '成长值不能为空!',
        ];

        $this->validate($data, $validate, $message);

        if(UserLevelService::save($data)){
            return success();
        }else{
            return failure();
        }
    }

    //更新
    public function edit(){
        $data = $this->request->post();
        //验证规则
        $validate = [
            'id' => 'require',
            'level_name'  => 'require',
            'growth_value'  => 'require',
        ];
        //提示信息
        $message = [
            'id.require' => '请选择要更新的数据!',
            'level_name.require' => '等级名称不能为空!',
            'growth_value.require' => '成长值不能为空!',
        ];

        $this->validate($data, $validate, $message);

        if(UserLevelService::update($data)){
            return success();
        }else{
            return failure();
        }
    }

    //删除
    public function delete(){
        $ids = $this->request->get("ids");
        if(empty($ids)){
            return failure(config('error.er5')['code'],"请选择要删除的数据");
        }

        UserLevelService::destroy($ids);

        return success();
    }

}

3.7完善会员列表

之前我们查询用户列表的时候,返回的用户等级level字段是一个整型,它存储的是ds_user_level这个表的自增ID,因此我们需要在查询用户列表的时候把level_name给查出来。

打开UserLevelService.php,新增一个方法

/**
 * 根据ID获取名称
 * @param  string  $id  等级ID
 * @return string
*/
public static function getLevelNameById($id){
    $list = self::list();
    $levelName = '';
    foreach($list as $v){
        if($v['id'] == $id){
            $levelName = $v['level_name'];
            break;
        }
    }

    return $levelName;
}

这段代码先调用list()方法,获取所有用户等级,然后再根据传进来的ID去获取相对于的等级名称

接下来打开UserService.php,找到之前写好的page($param)方法,在循环体里面新增如下一行代码即可

foreach ($data['list']  as $k => $v) {
    $data['list'][$k]['status_text'] = UserModel::$status[$v['status']];
    //新加的
    $data['list'][$k]['level_name'] = UserLevelService::getLevelNameById($v['level']);
}

这里我并没有使用连表查询,其实开发过程中尽量不要连表查,特别是表数据量大,连的表比较多的时候更加不要连表。像上面这种情况是可以连表的,因为等级表的数据量就是那么几条,连表也无所谓。

另外还有一个地方,上诉代码中在循环体中调用了getLevelNameById()这个方法,需要注意的是如果这个方法里面每次都要访问数据库,就不建议这样去做,因为这会增加数据库的压力,千万别怀疑这个东西,当你的循环体有好几个都是这么写,系统越来越庞大,数据量越来越多,性能肯定会下降的,因此我们从一开始就要杜绝这种情况的发生。

这里我之所有那么写,是因为getLevelNameById($id)这个方法的实现理论上是不用查数据库的,即使要查也是最多查一次,因为用户等级全部数据都是存储在redis缓存中。

相信大家也知道为啥我不这样实现这个方法

public static function getLevelNameById($id){
 	$level = UserLevelModel::find($id);
    return $level->level_name;
}

注:

上面这种实现逻辑主要是针对表数据比较少的情况,如果说用户等级的数据也几十万条,甚至更多,那这里就不能这样实现,因为你不太可能一次性查出所有数据

3.8会员注册默认等级

现在回到注册那一块,注册的时候我们需要给一个默认的会员等级,一般都是最普通的等级。

打开pc/service/PuserService.php,找到之前写的register($data)方法,新增如下代码

public static function save($data){
    //验证该账号是否存在
    if(UserModel::getByAccount($data['account'])){
        failure(config('error.er8')['code'],config('error.er8')['msg']);
    }

    //验证手机号是否存在
    if(UserModel::getByMobile($data['mobile'])){
        failure(config('error.er9')['code'],config('error.er9')['msg']);
    }

    //默认会员等级----这是新增的代码
    $level = UserLevelService::list();
    $data['level'] = empty($level) ? 0 : $level[0]['id'];

    $data['password'] = createPassword($data['password']);
    $user = new UserModel;
    $user->save($data);
    return $user->id;
}

这里调用UserLevelService::list()获取会员等级列表,这里的会员等级列表获取是根据成长值字段排序输出的,排在第一个的肯定是最开始的一个等级。

附录

1.建表语句

1.1会员表

CREATE TABLE `ds_user`  (
  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `user_sn` char(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '会员码',
  `account` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '账号',
  `password` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '密码',
  `nickname` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '昵称',
  `avatar` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '头像',
  `mobile` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '手机',
  `level` tinyint(4) NULL DEFAULT 0 COMMENT '等级',
  `sex` tinyint(1) NULL DEFAULT 0 COMMENT '性别:0-未知;1-男;2-女',
  `birthday` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '生日',
  `user_integral` int(11) NULL DEFAULT 0 COMMENT '积分',
  `user_growth` int(11) NULL DEFAULT 0 COMMENT '成长值',
  `login_time` datetime(0) NULL DEFAULT NULL COMMENT '最后登录时间',
  `login_ip` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '最后登录IP',
  `status` tinyint(4) NULL DEFAULT 1 COMMENT '状态:0-禁用 1-启用',
  `is_first_login` tinyint(1) NULL DEFAULT 0 COMMENT '第一次登录:0-未登录 1-已经登录过',
  `creator` int(11) NULL DEFAULT 0 COMMENT '创建人',
  `updator` int(11) NULL DEFAULT 0 COMMENT '更新人',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  `delete_time` datetime(1) NULL DEFAULT NULL COMMENT '删除时间,默认为空',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `idx_account`(`account`) USING BTREE,
  INDEX `idx_mobile`(`mobile`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
字段类型备注
idint(11) unsigned主键(PRIMARY)
user_snchar(20)会员码
accountvarchar(30)账号
passwordvarchar(80)密码
nicknamevarchar(30)昵称
avatarvarchar(200)头像
mobilevarchar(11)手机
leveltinyint(4)等级
sextinyint(1)性别:0-未知;1-男;2-女
birthdayvarchar(20)生日
user_integralint(11)积分
user_growthint(11)成长值
login_timedatetime最后登录时间
login_ipvarchar(30)最后登录IP
statustinyint(4)状态:0-禁用 1-启用
is_first_logintinyint(1)第一次登录:0-未登录 1-已经登录过
creatorint(11)创建人
updatorint(11)更新人
create_timedatetime创建时间
update_timedatetime更新时间
delete_timedatetime(1)删除时间,默认为空
字段类型备注
idint(10) unsigned主键(PRIMARY)
typetinyint(4)类型:1-签到,2-下单
user_idint(11)用户ID
integralint(11)积分
creatorint(11)创建者ID
updatorint(1)更新者ID
create_timedatetime创建时间
update_timedatetime更新时间
delete_timedatetime删除时间

1.3会员等级表

CREATE TABLE `ds_user_level`  (
  `id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `level_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '等级名称',
  `growth_value` int(11) NULL DEFAULT 0 COMMENT '成长值',
  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '等级备注',
  `image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '等级图标',
  `privilege` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '等级权益',
  `discount` decimal(11, 1) NULL COMMENT '等级折扣',
  `creator` int(11) NULL DEFAULT 0 COMMENT '创建者',
  `updator` int(11) NULL DEFAULT 0 COMMENT '更新者',
  `create_time` datetime NULL COMMENT '创建时间',
  `update_time` datetime NULL COMMENT '更新时间',
  `delete_time` datetime NULL COMMENT '删除时间',
  PRIMARY KEY (`id`)
) COMMENT = '会员等级表';
字段类型备注
idint(10) unsigned主键(PRIMARY)
level_namevarchar(30)等级名称
growth_valueint(11)成长值
remarkvarchar(255)等级备注
imagevarchar(255)等级图标
privilegevarchar(255)等级权益
discountdecimal(11,1)等级折扣
creatorint(11)创建者
updatorint(11)更新者
create_timedatetime创建时间
update_timedatetime更新时间
delete_timedatetime删除时间
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值