php 修改zen-cart下单和付款流程以防止漏单


Posted in PHP onMarch 08, 2010

用过zen-cart的人都知道,zen-cart中下单步骤是下面这样的(其中[]中的表示不是必须的):

   1. 购物车(shopping cart)

   2. [货运方式(delivery method)]

   3. 支付方式(payment method)

   4. 订单确认(confirmation)

   5. [第三方网站支付]

   6. 订单处理(checkout process)——这一步比较重要,因为会在这里将购物车中的信息写入订单

   7. 下单成功(checkout success)

   这样的流程在正常情况下是没有任何问题的。但是,从第5步到第6部的过程中,用户可能以为付款成功就直接关闭掉网页了,或者由于网络原因造成不能正常跳转到checkout_process页面,这样造成的后果是很严重的,因为订单不能被正常的创建。

   基于上述的分析, 我们希望稍微地改变一下流程,即在支付之前订单已经创建好了,这样就算在支付时不能从第三方支付网站跳转回来,我们也不会存在用户付款成功却在后台没有订单的情况了。经过修改后的蓝图基本是下面这样的:

   1. 在checkour_confirmation页面确认订单后,都会直接proccess,并且进入checkour_success页面,可以在这里进入付款页面。如下图所示:

     php 修改zen-cart下单和付款流程以防止漏单

   2. 如果当时客户没能付款,也可进入自己的后台对历史订单进行付款。如下图所示:

     php 修改zen-cart下单和付款流程以防止漏单

   下面我们就来看看如何一步一步来实现上述的功能。

  1. 首先我们需要对现有的支付模块进行一个改造。需要对支付方式的class增加一个字段paynow_action_url,用来表示进行支付的页面url,另外还需要增加一个函数,paynow_button($order_id),来获取支付表单的参数隐藏域代码。
要增加paynow_action_url字段,请在类payment的构造函数中最后加上下面的代码:

if ( (zen_not_null($module)) && (in_array($module.'.php', $this->modules)) && (isset($GLOBALS[$module]->paynow_action_url)) ) { 
$this->paynow_action_url = $GLOBALS[$module]->paynow_action_url; 
}

要增加paynow_button($order_id)函数,请在payment类的最后一个函数之后加上如下的代码:
function paynow_button($order_id){ 
if (is_array($this->modules)) { 
if (is_object($GLOBALS[$this->selected_module])) { 
return $GLOBALS[$this->selected_module]->paynow_button($order_id); 
} 
} 
}

2. 以paypal支付方式为例子,说明如何具体实现。为了不破坏paypal原有的代码,我们将paypal.php文件拷贝一个副本出来,并命名为paypalsimple.php,并对里面的代码做适当的修改。代码如下所示,可以看到,这里去掉了对form_action_url的指定,并给定了paynow_action_url,因为我们希望用户点击“确认订单”后直接进入checkout_process,所以如果不指定form_action_url,那么确认订单的表单就会直接提交到checkout_process页面了,而paynow_action_url就是以前的form_action_url的值。paynow_button函数的实现也很简单,这里只是将原先的process_button()函数的内容剪切过来而已,只不过我们没有使用全局的$order变量,而是使用$order = new order($order_id),来重新构造的一个对象,这样做是为在历史订单中显示pay now按钮做准备的。
paypalsimple.php
<?php 
/** 
* @package paypalsimple payment module 
* @copyright Copyright 2003-2006 Zen Cart Development Team 
* @copyright Portions Copyright 2003 osCommerce 
* @license http://www.zen-cart.com/license/2_0.txt GNU Public License V2.0 
* @version $Id: paypalsimple.php 4960 2009-12-29 11:46:46Z gary $ 
*/ 
// ensure dependencies are loaded 
include_once((IS_ADMIN_FLAG === true ? DIR_FS_CATALOG_MODULES : DIR_WS_MODULES) . 'payment/paypal/paypal_functions.php'); 
class paypalsimple { 
var $code, $title, $description, $enabled; 
// class constructor 
function paypalsimple() { 
global $order; 
$this->code = 'paypalsimple'; 
$this->title = MODULE_PAYMENT_PAYPAL_SIMPLE_TEXT_TITLE; 
if(IS_ADMIN_FLAG === true){ 
$this->title = MODULE_PAYMENT_PAYPAL_SIMPLE_TEXT_ADMIN_TITLE; 
} 
$this->description = MODULE_PAYMENT_PAYPAL_SIMPLE_TEXT_DESCRIPTION; 
$this->sort_order = MODULE_PAYMENT_PAYPAL_SIMPLE_SORT_ORDER; 
$this->enabled = ((MODULE_PAYMENT_PAYPAL_SIMPLE_STATUS == 'True') ? true : false); 
if ((int)MODULE_PAYMENT_PAYPAL_SIMPLE_ORDER_STATUS_ID > 0) { 
$this->order_status = MODULE_PAYMENT_PAYPAL_SIMPLE_ORDER_STATUS_ID; 
} 
$this->paynow_action_url = 'https://' . MODULE_PAYMENT_PAYPAL_SIMPLE_HANDLER; 
if (is_object($order)) $this->update_status(); 
} 
// class methods 
function update_status() { 
global $order, $db; 
if ( ($this->enabled == true) && ((int)MODULE_PAYMENT_PAYPAL_SIMPLE_ZONE > 0) ) { 
$check_flag = false; 
$check = $db->Execute("select zone_id from " . TABLE_ZONES_TO_GEO_ZONES . " where geo_zone_id = '" . MODULE_PAYMENT_PAYPAL_SIMPLE_ZONE . "' and zone_country_id = '" . $order->billing['country']['id'] . "' order by zone_id"); 
while (!$check->EOF) { 
if ($check->fields['zone_id'] < 1) { 
$check_flag = true; 
break; 
} elseif ($check->fields['zone_id'] == $order->billing['zone_id']) { 
$check_flag = true; 
break; 
} 
$check->MoveNext(); 
} 
if ($check_flag == false) { 
$this->enabled = false; 
} 
} 
} 
function javascript_validation() { 
return false; 
} 
function selection() { 
$text = MODULE_PAYMENT_SIMPLE_PAYPAL_TEXT_CATALOG_LOGO.'  '.MODULE_PAYMENT_PAYPAL_SIMPLE_TEXT_TITLE . '<br/><br/>    <span class="smallText">' . MODULE_PAYMENT_PAYPAL_SIMPLE_ACCEPTANCE_MARK_TEXT . '</span><br/><br/>'; 
return array('id' => $this->code, 
'module' => $text 
); 
} 
function pre_confirmation_check() { 
return false; 
} 
function confirmation() { 
return false; 
} 
function process_button() { 
return false; 
} 
function before_process() { 
return false; 
} 
function after_process() { 
return false; 
} 
function get_error() { 
return false; 
} 
function check() { 
global $db; 
if (!isset($this->_check)) { 
$check_query = $db->Execute("select configuration_value from " . TABLE_CONFIGURATION . " where configuration_key = 'MODULE_PAYMENT_PAYPAL_SIMPLE_STATUS'"); 
$this->_check = $check_query->RecordCount(); 
} 
return $this->_check; 
} 
function install() { 
global $db; 
$db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Enable PayPal-Simple Module', 'MODULE_PAYMENT_PAYPAL_SIMPLE_STATUS', 'True', 'Do you want to accept PayPal-Simple payments?', '6', '0', 'zen_cfg_select_option(array(\'True\', \'False\'), ', now())"); 
$db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('Sort order of display.', 'MODULE_PAYMENT_PAYPAL_SIMPLE_SORT_ORDER', '0', 'Sort order of display. Lowest is displayed first.', '6', '8', now())"); 
$db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, use_function, set_function, date_added) values ('Payment Zone', 'MODULE_PAYMENT_PAYPAL_SIMPLE_ZONE', '0', 'If a zone is selected, only enable this payment method for that zone.', '6', '2', 'zen_get_zone_class_title', 'zen_cfg_pull_down_zone_classes(', now())"); 
$db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, use_function, date_added) values ('Set Order Status', 'MODULE_PAYMENT_PAYPAL_SIMPLE_ORDER_STATUS_ID', '0', 'Set the status of orders made with this payment module to this value', '6', '0', 'zen_cfg_pull_down_order_statuses(', 'zen_get_order_status_name', now())"); 
$db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Mode for PayPal web services<br /><br />Default:<br /><code>www.paypal.com/cgi-bin/webscr</code><br />or<br /><code>www.paypal.com/us/cgi-bin/webscr</code><br />or for the UK,<br /><code>www.paypal.com/uk/cgi-bin/webscr</code>', 'MODULE_PAYMENT_PAYPAL_SIMPLE_HANDLER', 'www.paypal.com/cgi-bin/webscr', 'Choose the URL for PayPal live processing', '6', '73', '', now())"); 
} 
function remove() { 
global $db; 
$db->Execute("delete from " . TABLE_CONFIGURATION . " where configuration_key in ('" . implode("', '", $this->keys()) . "')"); 
} 
function keys() { 
return array('MODULE_PAYMENT_PAYPAL_SIMPLE_STATUS','MODULE_PAYMENT_PAYPAL_SIMPLE_SORT_ORDER','MODULE_PAYMENT_PAYPAL_SIMPLE_ZONE','MODULE_PAYMENT_PAYPAL_SIMPLE_ORDER_STATUS_ID', 'MODULE_PAYMENT_PAYPAL_SIMPLE_HANDLER'); 
} 
function paynow_button($order_id){ 
global $db, $order, $currencies, $currency; 
require_once(DIR_WS_CLASSES . 'order.php'); 
$order = new order($order_id); 
$options = array(); 
$optionsCore = array(); 
$optionsPhone = array(); 
$optionsShip = array(); 
$optionsLineItems = array(); 
$optionsAggregate = array(); 
$optionsTrans = array(); 
$buttonArray = array(); 
$this->totalsum = $order->info['total']; 
// save the session stuff permanently in case paypal loses the session 
$_SESSION['ppipn_key_to_remove'] = session_id(); 
$db->Execute("delete from " . TABLE_PAYPAL_SESSION . " where session_id = '" . zen_db_input($_SESSION['ppipn_key_to_remove']) . "'"); 
$sql = "insert into " . TABLE_PAYPAL_SESSION . " (session_id, saved_session, expiry) values ( 
'" . zen_db_input($_SESSION['ppipn_key_to_remove']) . "', 
'" . base64_encode(serialize($_SESSION)) . "', 
'" . (time() + (1*60*60*24*2)) . "')"; 
$db->Execute($sql); 
$my_currency = select_pp_currency(); 
$this->transaction_currency = $my_currency; 
$this->transaction_amount = ($this->totalsum * $currencies->get_value($my_currency)); 
$telephone = preg_replace('/\D/', '', $order->customer['telephone']); 
if ($telephone != '') { 
$optionsPhone['H_PhoneNumber'] = $telephone; 
if (in_array($order->customer['country']['iso_code_2'], array('US','CA'))) { 
$optionsPhone['night_phone_a'] = substr($telephone,0,3); 
$optionsPhone['night_phone_b'] = substr($telephone,3,3); 
$optionsPhone['night_phone_c'] = substr($telephone,6,4); 
$optionsPhone['day_phone_a'] = substr($telephone,0,3); 
$optionsPhone['day_phone_b'] = substr($telephone,3,3); 
$optionsPhone['day_phone_c'] = substr($telephone,6,4); 
} else { 
$optionsPhone['night_phone_b'] = $telephone; 
$optionsPhone['day_phone_b'] = $telephone; 
} 
} 
$optionsCore = array( 
'charset' => CHARSET, 
'lc' => $order->customer['country']['iso_code_2'], 
'page_style' => MODULE_PAYMENT_PAYPAL_PAGE_STYLE, 
'custom' => zen_session_name() . '=' . zen_session_id(), 
'business' => MODULE_PAYMENT_PAYPAL_BUSINESS_ID, 
'return' => zen_href_link(FILENAME_PAY_SUCCESS, 'referer=paypal', 'SSL'), 
'cancel_return' => zen_href_link(FILENAME_PAY_FAILED, '', 'SSL'), 
'shopping_url' => zen_href_link(FILENAME_SHOPPING_CART, '', 'SSL'), 
'notify_url' => zen_href_link('ipn_main_handler.php', '', 'SSL',false,false,true), 
'redirect_cmd' => '_xclick', 
'rm' => 2, 
'bn' => 'zencart', 
'mrb' => 'R-6C7952342H795591R', 
'pal' => '9E82WJBKKGPLQ', 
); 
$optionsCust = array( 
'first_name' => replace_accents($order->customer['firstname']), 
'last_name' => replace_accents($order->customer['lastname']), 
'address1' => replace_accents($order->customer['street_address']), 
'city' => replace_accents($order->customer['city']), 
'state' => zen_get_zone_code($order->customer['country']['id'], $order->customer['zone_id'], $order->customer['zone_id']), 
'zip' => $order->customer['postcode'], 
'country' => $order->customer['country']['iso_code_2'], 
'email' => $order->customer['email_address'], 
); 
if ($order->customer['suburb'] != '') $optionsCust['address2'] = $order->customer['suburb']; 
if (MODULE_PAYMENT_PAYPAL_ADDRESS_REQUIRED == 2) $optionsCust = array( 
'address_name' => replace_accents($order->customer['firstname'] . ' ' . $order->customer['lastname']), 
'address_street' => replace_accents($order->customer['street_address']), 
'address_city' => replace_accents($order->customer['city']), 
'address_state' => zen_get_zone_code($order->customer['country']['id'], $order->customer['zone_id'], $order->customer['zone_id']), 
'address_zip' => $order->customer['postcode'], 
'address_country' => $order->customer['country']['title'], 
'address_country_code' => $order->customer['country']['iso_code_2'], 
'payer_email' => $order->customer['email_address'], 
); 
$optionsShip = array( 
//'address_override' => MODULE_PAYMENT_PAYPAL_ADDRESS_OVERRIDE, 
'no_shipping' => MODULE_PAYMENT_PAYPAL_ADDRESS_REQUIRED, 
); 
if (MODULE_PAYMENT_PAYPAL_DETAILED_CART == 'Yes') $optionsLineItems = ipn_getLineItemDetails(); 
if (sizeof($optionsLineItems) > 0) { 
$optionsLineItems['cmd'] = '_cart'; 
// $optionsLineItems['num_cart_items'] = sizeof($order->products); 
if (isset($optionsLineItems['shipping'])) { 
$optionsLineItems['shipping_1'] = $optionsLineItems['shipping']; 
unset($optionsLineItems['shipping']); 
} 
if (isset($optionsLineItems['handling'])) { 
$optionsLineItems['handling_1'] = $optionsLineItems['handling']; 
unset($optionsLineItems['handling']); 
} 
unset($optionsLineItems['subtotal']); 
// if line-item details couldn't be kept due to calculation mismatches or discounts etc, default to aggregate mode 
if (!isset($optionsLineItems['item_name_1'])) $optionsLineItems = array(); 
//if ($optionsLineItems['amount'] != $this->transaction_amount) $optionsLineItems = array(); 
ipn_debug_email('Line Item Details (if blank, this means there was a data mismatch, and thus bypassed): ' . "\n" . print_r($optionsLineItems, true)); 
} 
$products_name_display = ""; 
/* 
for ($i=0, $n=sizeof($order->products); $i<$n; $i++) { 
if(i > 0) { 
$products_name_display.= ', '; 
} 
$products_name_display.= $order->products[$i]['name']. '('. $order->products[$i]['qty'] .','.$order->products[$i]['dhisys_web_order_number'].')'; 
}*/ 
$optionsAggregate = array( 
'cmd' => '_ext-enter', 
'item_name' => $products_name_display, 
'item_number' => $order_id, 
'num_cart_items' => sizeof($order->products), 
'amount' => number_format($this->transaction_amount, $currencies->get_decimal_places($my_currency)), 
'shipping' => '0.00', 
); 
if (MODULE_PAYMENT_PAYPAL_TAX_OVERRIDE == 'true') $optionsAggregate['tax'] = '0.00'; 
if (MODULE_PAYMENT_PAYPAL_TAX_OVERRIDE == 'true') $optionsAggregate['tax_cart'] = '0.00'; 
$optionsTrans = array( 
'upload' => (int)(sizeof($order->products) > 0), 
'currency_code' => $my_currency, 
// 'paypal_order_id' => $paypal_order_id, 
//'no_note' => '1', 
//'invoice' => '', 
); 
// if line-item info is invalid, use aggregate: 
if (sizeof($optionsLineItems) > 0) $optionsAggregate = $optionsLineItems; 
// prepare submission 
$options = array_merge($optionsCore, $optionsCust, $optionsPhone, $optionsShip, $optionsTrans, $optionsAggregate); 
ipn_debug_email('Keys for submission: ' . print_r($options, true)); 
if(sizeof($order->products) > 0){ 
$options['cmd'] = '_cart'; 
for ($i=0, $n=sizeof($order->products); $i<$n; $i++) { 
$options['item_name_'. (string)($i+1)] = $order->products[$i]['name']; 
$options['item_number_'. (string)($i+1)] = $order->products[$i]['dhisys_web_order_number']; 
$options['amount_'. (string)($i+1)] = number_format((float)$order->products[$i]['final_price'],2); 
$options['quantity_'. (string)($i+1)] = $order->products[$i]['qty']; 
} 
} 
// build the button fields 
foreach ($options as $name => $value) { 
// remove quotation marks 
$value = str_replace('"', '', $value); 
// check for invalid chars 
if (preg_match('/[^a-zA-Z_0-9]/', $name)) { 
ipn_debug_email('datacheck - ABORTING - preg_match found invalid submission key: ' . $name . ' (' . $value . ')'); 
break; 
} 
// do we need special handling for & and = symbols? 
//if (strpos($value, '&') !== false || strpos($value, '=') !== false) $value = urlencode($value); 
$buttonArray[] = zen_draw_hidden_field($name, $value); 
} 
$_SESSION['paypal_transaction_info'] = array($this->transaction_amount, $this->transaction_currency); 
$process_button_string = implode("\n", $buttonArray) . "\n"; 
return $process_button_string; 
} 
} 
?>

3. 在checkout_success页面中显示pay now按钮。打开文件"includes/modules/pages/checkout_success/header.php",在文件的末尾添加下面的代码(如果你已经掌握zen-cart中的通知者/观察者模式,并且又不想破坏zen-cart核心代码的话,也可以创建一个观察类来监听NOTIFY_HEADER_END_CHECKOUT_SUCCESS来实现)。
require_once(DIR_WS_CLASSES . 'order.php'); 
require_once(DIR_WS_CLASSES . 'payment.php'); 
$payment_modules = new payment($orders->fields['payment_module_code']);

打开文件"includes/modules/templates/template_default/templates/tpl_checkout_success_default.php",并在适当的位置加上如下的代码,这里对订单的状态进行了一个判断,当只有订单的状态在未付款状态,才显示该按钮,
<div id="pay_now"> 
<?php 
//&& $orders->fields['orders_status'] == '1' 
if(isset($payment_modules->paynow_action_url) && $payment_modules->paynow_action_url != ''&& $orders->fields['orders_status'] == '1'){ 
echo('<fieldset id="csNotifications">'); 
echo('<legend>'.TEXT_PAYNOW.'</legend>'); 
echo zen_draw_form('checkout_paynow', $payment_modules->paynow_action_url, 'post', 'id="checkout_confirmation" onsubmit="submitonce();"'); 
$selection = $payment_modules->selection(); 
echo('<div class="buttonRow payment_method">'.$selection[0]['module'].'</div>'); 
echo('<div class="buttonRow forward paynow">'); 
if (is_array($payment_modules->modules)) { 
echo $payment_modules->paynow_button($orders_id); 
} 
echo(zen_image_submit(BUTTON_IMAGE_PAYNOW, BUTTON_IMAGE_PAYNOW_ALT, 'name="btn_paynow" id="btn_paynow"')); 
echo('</div>'); 
echo('</form>'); 
echo('</fieldset>'); 
} 
?> 
</div>

4. 在历史订单中显示pay now按钮。需要显示pay now按钮的页面有三个:account, account_history,account_history_info,这里的实现和checkout_success页面的实现大同小异,只是传给$payment_modules的函数paynow_button的参数不一样而已,这里就不再赘述。
总结:
经过上面的修改,我们的流程如下:
1. 购物车(shopping cart)
2. [货运方式(delivery method)]
3. 支付方式(payment method)
4. 订单确认(confirmation)
5. 订单处理(checkout process)
6. 下单成功(checkout success)
7. [第三方网站支付]
因为从订单确认到订单处理,都是在我们自己的网站完成的,并且进入支付网站之前,订单已经存在了,这样就不会出现掉单的情况了。
PHP 相关文章推荐
PHP开发文件系统实例讲解
Oct 09 PHP
PHP 日期时间函数的高级应用技巧
Oct 10 PHP
Ajax PHP 边学边练 之三 数据库
Nov 26 PHP
php切割页面div内容的实现代码分享
Jul 31 PHP
php实现专业获取网站SEO信息类实例
Apr 02 PHP
YII Framework框架教程之日志用法详解
Mar 14 PHP
解决form中action属性后面?传递参数 获取不到的问题
Jul 21 PHP
Yii2框架中使用PHPExcel导出Excel文件的示例
Aug 09 PHP
php中输出json对象的值(实现方法)
Mar 07 PHP
PHP中的访问修饰符简单比较
Feb 02 PHP
PHP实现的微信APP支付功能示例【基于TP5框架】
Sep 16 PHP
php和html的区别点详细总结
Sep 24 PHP
PHP 最大运行时间 max_execution_time修改方法
Mar 08 #PHP
php ss7.5的数据调用 (笔记)
Mar 08 #PHP
phpmyadmin 常用选项设置详解版
Mar 07 #PHP
PHPMYADMIN 简明安装教程 推荐
Mar 07 #PHP
THINKPHP+JS实现缩放图片式截图的实现
Mar 07 #PHP
PHP用mysql数据库存储session的代码
Mar 05 #PHP
PHP 采集程序原理分析篇
Mar 05 #PHP
You might like
php把session写入数据库示例
2014/02/26 PHP
使用配置类定义Codeigniter全局变量
2014/06/12 PHP
如何直接访问php实例对象中的private属性详解
2017/10/12 PHP
提高代码性能技巧谈—以创建千行表格为例
2006/07/01 Javascript
window.open以post方式将内容提交到新窗口
2012/12/26 Javascript
js分页代码分享
2014/04/28 Javascript
javascript 获取元素样式必杀技
2014/05/04 Javascript
JS实现的简单鼠标跟随DiV层效果完整实例
2015/10/31 Javascript
基于jQuery的checkbox全选问题分析
2016/11/18 Javascript
详解如何较好的使用js
2016/12/16 Javascript
js仿微信公众平台打标签功能
2017/04/08 Javascript
js实现会跳动的日历效果(完整实例)
2017/10/18 Javascript
还不懂递归?读完这篇文章保证你会懂
2018/07/29 Javascript
vue的全局变量和全局拦截请求器的示例代码
2018/09/13 Javascript
react-router4按需加载(踩坑填坑)
2019/01/06 Javascript
微信小程序--获取用户地理位置名称(无须用户授权)的方法
2019/04/29 Javascript
关于vue路由缓存清除在main.js中的设置
2019/11/06 Javascript
详解React 元素渲染
2020/07/07 Javascript
python实现的重启关机程序实例
2014/08/21 Python
python基础教程之循环介绍
2014/08/29 Python
Python实现Windows和Linux之间互相传输文件(文件夹)的方法
2017/05/08 Python
详解Python字符串切片
2019/05/20 Python
Django vue前后端分离整合过程解析
2020/11/20 Python
naturalizer加拿大官网:美国娜然女鞋
2017/04/04 全球购物
美国运动鞋和运动服零售商:Footaction
2017/04/07 全球购物
戴尔英国翻新电脑和电子产品:Dell UK Refurbished Computers
2019/07/30 全球购物
餐饮业会计岗位职责
2013/12/19 职场文书
高中生毕业自我鉴定范文
2013/12/22 职场文书
幼儿园亲子活动方案
2014/01/29 职场文书
采购员岗位职责范本
2015/04/07 职场文书
试用期旷工辞退通知书
2015/04/17 职场文书
英语读书笔记
2015/07/02 职场文书
标准演讲稿格式结尾应该怎么书写?
2019/07/17 职场文书
正确的理解和使用Django信号(Signals)
2021/04/14 Python
redis客户端实现高可用读写分离的方式详解
2021/07/04 Redis
python百行代码实现汉服圈图片爬取
2021/11/23 Python