分类 Webman 下的文章

方案一

Casbin的核心-表.png

Casbin的核心.png

方案二

111.JPG
原理

222.JPG
规则

333.JPG
数据ER

444.PNG
管理UI

555.JPG
数据表实例

特别声明:其中方案二为某大佬线上生产环境系统设计,如果侵犯到此权限请通知我删除,同时采纳者一同承担相应责任。

评论表

idarticle_idparent_id(string)
130
231
331_2
432
531_2_3

模型操作

删除 id = 1 的1 级文章及下级所有文章
Article::where('id', 1)->delete();
Article::where('parent_id', 'like', '1%')->delete();
这样和 id = 1 的所有相关文章就删除了
删除这些文章下的评论就用观察者就行了



第一步:

习惯将CDN文件下载到本地,保存至静态目录中,注意保留版本号。

<!-- 引入 css -->
<link href="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet">
<!-- 引入 js -->
<script src="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/index.min.js"></script>

第二步:

直接上 HTML代码吧:

<div class="layui-row">
            <div class="layui-col-md12">
                <div class="layui-form-item">
                    <label class="layui-form-label">正文:</label>
                    <div class="layui-input-block">
                        <textarea name="content" id="content" style="display: none"></textarea>
                        <div id="toolbar-container"></div>
                        <div id="editor-container" style="height: 300px;border:1px solid #dddddd"></div>
                    </div>
                </div>
            </div>
        </div>


第三步:

注意从官方拷贝js代码时,要注意javascript与typescript的修改。
直接上 JS代码。

// 渲染富文本编辑器
const { createEditor, createToolbar } = window.wangEditor;
// 编辑器配置
const editorConfig = {MENU_CONF: {}};
// 图片上传
editorConfig.MENU_CONF['uploadImage'] = {
    // 表单字段名称
    fieldName: 'file',
    // 单个文件的最大体积限制,默认为 2M
    maxFileSize: 5 * 1024 * 1024, // 5M
    // 最多可上传几个文件,默认为 100
    maxNumberOfFiles: 5,
    // 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
    allowedFileTypes: ['image/*'],
    // 小于该值就插入 base64 格式(而不上传),默认为 0
    base64LimitSize: 5 * 1024, // 5kb
    // 自定义参数
    meta: {
        from: 'editor', //标志是从编辑器上传
    },
    // 上传图片的配置
    server: '<?=route("upload.image")?>',
};
// 默认提示文字
editorConfig.placeholder = '请输入内容'
editorConfig.onChange = (editor) => {
    // 当编辑器选区、内容变化时,即触发
    //console.log('content', editor.children)
    //console.log('html', editor.getHtml())
    document.getElementById('content').value = editor.getHtml();
}


// 工具栏配置
const toolbarConfig = {}

// 创建编辑器
const editor = createEditor({
    html: document.getElementById('content').value,
    selector: '#editor-container',
    config: editorConfig,
    mode: 'simple' // 或 'simple' 参考下文
})
// 创建工具栏
const toolbar = createToolbar({
    editor,
    selector: '#toolbar-container',
    config: toolbarConfig,
    mode: 'simple' // 或 'simple' 参考下文
})

最后:

截图如下。

333333.png

直接上代码

<?php
namespace app\controller;

use support\Request;
use support\Response;
use Yansongda\Pay\Pay;

class Payment
{
    protected $config = [
        'alipay' => [
            'default' => [
                // 必填-支付宝分配的 app_id
                'app_id' => 'wx4926de289f70b113',
                // 必填-应用私钥 字符串或路径
                'app_secret_cert' => 'MIIEvLoLc1s/VdBHku+JEd0YmEY+p4sjmcRnlu4AlzLxkWUTTg==',
                // 必填-应用公钥证书 路径
                'app_public_cert_path' => '/Users/yansongda/pay/cert/appCertPublicKey_2016082000295641.crt',
                // 必填-支付宝公钥证书 路径
                'alipay_public_cert_path' => '/Users/yansongda/pay/cert/alipayCertPublicKey_RSA2.crt',
                // 必填-支付宝根证书 路径
                'alipay_root_cert_path' => '/Users/yansongda/pay/cert/alipayRootCert.crt',
                'return_url' => 'https://yansongda.cn/alipay/return',
                'notify_url' => 'https://yansongda.cn/alipay/notify',
                // 选填-第三方应用授权token
                'app_auth_token' => '',
                // 选填-服务商模式下的服务商 id,当 mode 为 Pay::MODE_SERVICE 时使用该参数
                'service_provider_id' => '',
                // 选填-默认为正常模式。可选为: MODE_NORMAL, MODE_SANDBOX, MODE_SERVICE
                'mode' => Pay::MODE_NORMAL,
            ]
        ],
        'wechat' => [
            'default' => [
                // 必填-商户号,服务商模式下为服务商商户号
                'mch_id' => '123xxxx02',
                // 必填-商户秘钥
                'mch_secret_key' => 'xxxxx',  //api安全中心里的V3密码32位的那个
                // 必填-商户私钥 字符串或路径
                'mch_secret_cert' => '/xxxxx/apiclient_key.pem',  //api安全中心里下载的的密钥,绝对路径
                // 必填-商户公钥证书路径
                'mch_public_cert_path' => '/xxxx/apiclient_cert.pem', //api安全中心里下载的公钥,绝对路径
                // 必填
                'notify_url' => 'https://www.xxx.com/member/wx_native_notify', //支付回调地址必须https
                // 选填-公众号 的 app_id
                'mp_app_id' => 'wx4926de289f70b113',
                // 选填-小程序 的 app_id
                'mini_app_id' => '',
                // 选填-app 的 app_id
                'app_id' => '',
                // 选填-合单 app_id
                'combine_app_id' => '',
                // 选填-合单商户号
                'combine_mch_id' => '',
                // 选填-服务商模式下,子公众号 的 app_id
                'sub_mp_app_id' => '',
                // 选填-服务商模式下,子 app 的 app_id
                'sub_app_id' => '',
                // 选填-服务商模式下,子小程序 的 app_id
                'sub_mini_app_id' => '',
                // 选填-服务商模式下,子商户id
                'sub_mch_id' => '',
                // 选填-微信公钥证书路径, optional,强烈建议 php-fpm 模式下配置此参数
                'wechat_public_cert_path' => [
                    '4248DC46520F9EAC26C7FET4464ADBE5ADDA3A' => '/xxxx/wechatpay_public.pem', //通过算法生成的,具体生成方式往下面看。
                ],
                // 选填-默认为正常模式。可选为: MODE_NORMAL, MODE_SERVICE
                'mode' => Pay::MODE_NORMAL,
            ]
        ],
        'logger' => [
            'enable' => false,
            'file' => './logs/alipay.log',
            'level' => 'info', // 建议生产环境等级调整为 info,开发环境为 debug
            'type' => 'single', // optional, 可选 daily.
            'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天
        ],
        'http' => [ // optional
            'timeout' => 5.0,
            'connect_timeout' => 5.0,
            // 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html)
        ],
    ];

    //二维码生成展示页
    public function index(Request $request)
    {
        Pay::config($this->config);

        $order = [
            'out_trade_no' => time().'',
            'description' => 'subject-测试',
            'amount' => [
                'total' => 1,
            ],
        ];

        $result = Pay::wechat()->scan($order);
        // 二维码内容: $qr = $result->code_url;
        //var_export($result->code_url);

        return view('payment/index', ['code_url' => $result->code_url]);
    }

    //支付成功回调
    public function verify(Request $request)
    {
        Pay::config($this->config);

        // 是的,你没有看错,就是这么简单!
        $result = Pay::wechat()->callback($request->post());
        file_put_contents(__DIR__ . '/notify_result_ysd.txt',
            date('Y-m-d H:i:s') . ':' . var_export($result, true));
        

        //这里很关键,需要注意。
        return new Response(200, [], json_encode(['code' => 'SUCCESS', 'message' => '成功']));
    }
}

微信支付平台证书下载:

http://www.liziyu.com/archives/309/

话不多说直接上代码:

// app\member\controller\pay.php

<?php

namespace app\controller;

use EasyWeChat\Kernel\Exceptions\Exception;
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
use support\Request;
use EasyWeChat\Pay\Application;
use Symfony\Component\HttpFoundation\HeaderBag;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;

class Index
{
    private $config = [
        'mch_id' => 'xxxx', //根据自已实际的填写
        // 商户证书
        'private_key' => '/xxx/apiclient_key.pem',//根据自已实际的填写,绝对路径
        'certificate' => '/xxx/apiclient_cert.pem', //根据自已实际的填写,绝对路径
        // v3 API 秘钥
        'secret_key' => 'xxxx', //根据自已实际的填写
        // v2 API 秘钥
        'v2_secret_key' => 'xxx', //根据自已实际的填写
        'platform_certs' => [
            '/xxx/wechatpay_public.pem', //根据自已实际的填写,获取办法见下面。
        ],
        'http' => [
            'throw' => true, // 状态码非 200、300 时是否抛出异常,默认为开启
            'timeout' => 5.0,
            // 'base_uri' => 'https://api.mch.weixin.qq.com/', // 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri
        ],
    ];

    //二维码生成展示页
    public function index(Request $request)
    {
        try {
            $app = new Application($this->config);
            $response = $app->getClient()->post('/v3/pay/transactions/native', [
                'json' => [
                    'mchid' => (string)$app->getMerchant()->getMerchantId(),
                    'out_trade_no' => 'SN_'.time(),
                    'appid' => 'xxxx', //根据自已实际的填写
                    'description' => '测试内容',
                    'notify_url' => 'https://www.xxxx.com/member/wx_native_notify', //注意是ssl协议
                    'amount' => [
                        'total' => 2,
                        'currency' => 'CNY',
                    ]
                ]
            ]);
        } catch (InvalidArgumentException $e) {
            //
        }
        return view('index/index', ['code_url' => $response->toArray()]);
    }


    //回调
    public function verify(Request $request)
    {
        try {
           //下面四行很关键
            $symfony_request = new SymfonyRequest($request->get(), $request->post(), [], $request->cookie(), [], [], $request->rawBody());
            $symfony_request->headers = new HeaderBag($request->header());
            $app = new Application($this->config);
            $app->setRequestFromSymfonyRequest($symfony_request);

            // $app 为你实例化的支付对象,此处省略实例化步骤
            $server = $app->getServer();
            
            // 处理支付结果事件
            $server->handlePaid(function ($message) {
                // $message 为微信推送的通知结果,详看微信官方文档
                file_put_contents(__DIR__ . '/notify_result.txt',
                    date('Y-m-d H:i:s') . ':' . var_export($message, true));
                
                // 微信支付订单号 $message['transaction_id']
                // 商户订单号 $message['out_trade_no']
                // 商户号 $message['mchid']
                // 具体看微信官方文档...
                // 进行业务处理,如存数据库等...

            });
            //这一步特别要注意,必须这么写。
            return $server->serve()->getBody()->getContents();

        } catch (Exception $e) {
            echo $e->getMessage();
        }
    }

}

“平台证书”申请办法:http://www.liziyu.com/archives/309/

一般来说在Worker::runAll();调用前运行的代码都是在主进程运行的,onXXX回调运行的代码都属于子进程。注意写在Worker::runAll();后面的代码永远不会被执行。

use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';

// 运行在主进程
$tcp_worker = new Worker("tcp://0.0.0.0:2347");
// 赋值过程运行在主进程
$tcp_worker->onMessage = function(TcpConnection $connection, $data)
{
    // 这部分运行在子进程
    $connection->send('hello ' . $data);
};

Worker::runAll();

注意代码中的注释哦!

注意:
1、不要在主进程中初始化数据库、memcacheredis等连接资源,因为主进程初始化的连接可能会被子进程自动继承(尤其是使用单例的时候),所有进程都持有同一个连接,服务端通过这个连接返回的数据在多个进程上都可读,会导致数据错乱。同样的,如果任何一个进程关闭连接(例如daemon模式运行时主进程会退出导致连接关闭),都导致所有子进程的连接都被一起关闭,并发生不可预知的错误,例如mysql gone away 错误。

2、推荐在onWorkerStart里面初始化连接资源。

转自:http://www.workerman.net

1、在文件support/helper.php添加:

function env($key, $default = null)
{
    static $env_config = [];
    if (!$env_config) {
        $env_config = include base_path() . DIRECTORY_SEPARATOR . '.env.php';
    }
    return $env_config[$key] ?? $default;
}

2、在项目根目录添加.env.php

<?php

// config/app.php
$app = [
    'APP_NAME' => 'webman',
    'APP_ENV' => 'dev',
    'APP_DEBUG' => true,
];

// config/database.php
$mysql = [
    'DB_HOST' => '127.0.0.1',
    'DB_PORT' => 3306,
    'DB_USER' => 'root',
    'DB_PASSWORD' => 'root',
];

//合并数组
return array_merge($app, $mysql);

3、在config/database.php中改为:

return [
    // 默认数据库
    'default' => 'mysql',

    // 各种数据库配置
    'connections' => [
        'mysql' => [
            'driver' => 'mysql',
            'host' => env("DB_HOST", '127.0.0.1'),
            'port' => env("DB_PORT", 3306),
            'database' => 'test',
            'username' => env('DB_USER', 'root'),
            'password' => env('DB_PASSWORD', 'root'),
            'unix_socket' => '',
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'strict' => true,
            'engine' => null,
        ],

注意:.env.php 不提交到版本库(.gitignore里加一行.env.php)。

本文转自:https://www.workerman.net/q/7564

server
{
    listen 80;
    server_name xxx.webman.xxx;
    
    index index.html;
    root /server/webman/public;
    
    location / {
        proxy_http_version 1.1;
        proxy_set_header Connection "keep-alive";
        proxy_set_header Host $http_host;
        
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-Scheme $scheme;
        
        if ($uri = /) {
            proxy_pass http://127.0.0.1:8787;
            break;
        }
        
        if (!-e $request_filename) {
            proxy_pass http://127.0.0.1:8787;
        }
    }
    
    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|js|css)$
    {
        expires      30d;
        error_log /dev/null;
        access_log /dev/null;
    }
    
    access_log    /dev/null;
    error_log    /server/logs/webman.error.log;
}