看到这个需求,先分析一下内容,看是不是有更简洁的方法实现。
1.卡号规则,虽然比较多内容,但是我们可以确认就是 固定字符+to_char(sysdate,‘YYDDMM’)+流水(使用for循环实现流水)
2.默认开卡类型,即默认传值
3.开卡店仓和手淘开卡店仓,实际上这两个字段和店仓表就是主外键关系。在传值时候可以仿照给开卡店仓传值的方法,来给手淘开卡店仓传值,关于扫码开卡或自选开卡,对于线下而言就是:'扫码门店’字段是否为空,我们可以使用这个字段进行验证,通俗来说,我用一下两张图就能解释开卡的这种逻辑
4.因为都是通过手机淘宝开卡,我们就默认传值:‘Y’,或者更合理的方式,在where条件后面判断来源,传’Y’。
5.关于绑卡。逻辑与开卡类似,其实就是在第三步上进行改动,将insert改为update
我们整理一下,其实都是可以从线下脚本调整实现,大致列为下面几类:
传门店值>新会员即insert,老会员即update>固定值传固定字符>卡号流水使用for循环实现递增。我还是用下面两个图表达这种绑卡会员身份的逻辑
sequenceDiagram
Title: ***老会员扫门店码绑定天猫卡***
手淘开卡门店-开卡门店: 我有女朋友啦!
Note right of 手淘开卡门店:开卡门店愣了一下,说:你tm有还跟我说?没有我借你
手淘开卡门店--手淘开卡门店:自给自足,衣食无忧。
先来第一步,卡号规则,难点在于流水号生成如何递增,先确定流水号,则有初始值。要将初始值存于表中,还要将递增后的值也更新进去。
在we_os表中设置流水卡号初始值为1。新增字段
alter table we_os add card_no NUMBER(10);
update we_os set card_no=1;
commit;
cardnolength := length(v_cardno);--获取流水号的长度2018/08/21
if cardnolength <> 5 then --流水号初始值为0,长度为1
while (5-cardnolength) >0 loop -- 判断流水号如果低于5位,就执行循环
v_cardno := 0||v_cardno; --每次循环都在流水号上加0,即直到为00001
cardnolength := cardnolength +1; --每次循环,都将卡号长度值加1
end loop;
end if; --循环结束,此时card_no变更为00001_cardno := cardprefix
||to_char(sysdate,'yymmdd')|| v_cardno; --卡号规则2018/08/22
--卡号=TM+180822+00001 即TM18082200001
update we_os set card_no = v_cardno+1 where appid=p_appid;--在每生成一张会员卡,card_no就加1,实现流水号递增
返回0,执行成功,现在我们看下卡号生成结果
但是考虑一个业务场景,如果有门店手工设置了卡号为TM18082300001,在18年8月23号如果有会员当天第一个开卡,系统生成的卡号势必和手工设置的卡号冲突。实际情况中还真发现了一例
我们可以看到触发时间,在毫秒以下,开卡提示违反数据库卡号唯一约束的这个人,实际上系统预生成的卡号就是TM18090900456,但是这个人的卡号在毫秒以前被上一个人开卡占用了。这种情况个人预估是数据库并发原因。
我们可以再加一层判断,预生成的卡号是否在系统中存在。存在,则流水号加1,直到预生成的卡号为唯一。
cardnolength := length(v_cardno);--获取流水号的长度
s_count :=5-cardnolength;--5减去流水号的长度,赋值
p_cardnostring:=NULL;--需要与流水号拼接成卡号的字符串,初始化为null
for i IN 1..s_count LOOP--for循环实现拼接的卡号字符串生成
p_cardnostring :=p_cardnostring||'0';
--如s_count为3,则p_cardnostring为000
end loop;
select count(cv.id) into c_count from c_client_vip cv
where cv.cardno=cardprefix||to_char(sysdate,'yymmdd')
||p_cardnostring||v_cardno;
--判断即将生成的会员卡号在系统中是否存在,并将结果赋值
while c_count!=0 loop
v_cardno:=v_cardno+1;
--如果卡号存在,在流水号上加1,继续循环,直到即将生成的卡号在系统不存在
select count(cv.id) into c_count from c_client_vip cv where
cv.cardno=cardprefix||to_char(sysdate,'yymmdd')
||p_cardnostring|| v_cardno;
--每循环一次,都对变量c_count重新赋值
end loop;
p_cardno := cardprefix ||to_char(sysdate,'yymmdd')||p_cardnostring|| v_cardno;
--卡号规则2018/09/10
现在开始实现开卡门店与手淘门店传值。
判断传入的开卡门店并赋值给开卡门店和手淘开卡门店
IF P_OPENCARDSTORE_CODE IS NOT NULL THEN--如果门店参数不为空
SELECT c.ID INTO P_OPENCARDSTORE_ID FROM C_STORE C WHERE C.CODE= P_OPENCARDSTORE_CODE; --将参数传给手淘开卡门店
ELSE --如果为空,即业务上理解为未扫门店码
SELECT W.c_Store_Id INTO P_OPENCARDSTORE_ID FROM c_client_vip W WHERE W.Id=p_c_vip_id;--将这个会员的手淘开卡门店设置为默认门店
END IF;--给手淘开卡门店P_OPENCARDSTORE_ID 赋值
update c_client_vip cc set cc.st_opencardstore_id=P_OPENCARDSTORE_ID,cc.wisdom_store='Y' where cc.mobil=p_phonenum;--将手淘开卡门店字段传给VIP档案,将是否天猫会员变更为'Y'
我们将生成卡号步骤挖深详细说明。五位流水号,如果开卡人数达到99999个,第100000个人卡号就是’TM180824100000 '流水号达到6位数了。所以再次与客户确认卡号规则。
最终确定每天凌晨,五位流水号还原,那么就可以满足:'日期+5位流水号’为唯一数值
还原动作分两步:
1.设定还原数据的脚本,以存储过程最佳
2.设置job任务调用数据库存储过程
直接来个简单粗暴的,不传参的存储过程
CREATE OR REPLACE PROCEDURE UPDATE_WEOS_CARDNUMBER IS
BEGIN
UPDATE WE_OS SET CARD_NO=0,MODIFIEDDATE=SYSDATE WHERE APPID='20180820115920';--每次被调用时候,把调用时间更新到we_os表中
COMMIT;
END;
测试一下!
测试结果成功!
脚本设定好了,接下来就是job定时器设置,直接贴上代码
declare JOBAUTO PLS_INTEGER;
BEGIN
sys.DBMS_JOB.submit(job=>JOBAUTO,
what=>'UPDATE_WEOS_CARDNUMBER;',--调用的存储过程名
NEXT_DATE=>TO_DATE('25-08-2018 00:00:00','DD-MM-YYYY HH24:MI:SS'),--下次执行时间
interval=>'trunc(sysdate+1)') ;--执行周期每24H一次
commit;
end;
至此在每天凌晨对流水号清零任务已启动。
以上关于oracle定时器的语法,如果是mysql定时器,请查阅文档
mysql创建定时器
改代码测试过程中遇到疑难问题一:
老的会员绑卡时候,按照要求,如果传参,保留原开卡门店,参数传给手淘开卡店仓,但是实际测试中发现,传参同时都更新了开卡门店和手淘开卡门店。
检查代码并将传参打印出来,确定是不是传参问题
jor.put('errMessage', '本次扫码传入门店参数是'||P_OPENCARDSTORE_ID);
确定不是参数问题,发现问题,见如下代码
--判断门店是以线上为准
if isonlinestore='Y' and v_storeid is not null then
update c_client_vip c set c.c_store_id=v_storeid,c.c_customer_id=v_customerid where c.id=p_c_vip_id;
else
if c_openstorecode is null and v_storeid is not null then --线下是空,就已线上为准
update c_client_vip c set c.c_store_id=v_storeid,c.c_customer_id=v_customerid where c.id=p_c_vip_id;
else
p_openstorecode:=c_openstorecode;
end IF;
以上代码是说,如果传入参数为空,门店以线上为准,但是这个会员的线下开卡门店有,那么就将参数更新进开卡门店字段。再来看看我们的测试请求参数,果然是以线上门店为准
ok,再次简单粗暴,确认以上代码不影响其它业务场景和流程后,赶紧注释注释注释掉!~
问题二:
设置会员表的手淘开卡门店与门店档案表主外键关联,无法创建主外键约束
alter table C_CLIENT_VIP
add constraint FK_STC_STORE foreign key (ST_OPENCARDSTORE_ID)
references C_STORE (ID) on delete set null;--设置主外键关系名FK_STC_STORE 当删除门店数据后,就将ST_OPENCARDSTORE_ID设置为null(空)
原来会员表的ST_OPENCARDSTORE_ID有部分数据外键值为0,排查发现0不在主键表的ID中,所以创建主外键时候,0无法在主键表的ID中找到,将ST_OPENCARDSTORE_ID设置为null
update C_CLIENT_VIP set ST_OPENCARDSTORE_ID=null where id=19088
通过以上的案例,我们整理一下流程:
1.对用户需求进行合理化分析,包含业务场景,需要怎么实现功能,对不合理的需求提出反驳意见以及修改建议
2.对需求按先后排序的方法,一步一步实现
3.对实际情况中发生未考虑全面的问题进行剖析和商讨解决方案
4.出现的各种新的问题,先自学,查百度,查资料,最后向专业请教,尽量独立完成
5.对每次的挑战,对问题和解决办法的汇报整理