我的购物网站

Oct 25, 2016


  最近在搭建一个购物网站,目的是希望把自己所学到的东西都用起来,串在一起,做一个阶段的总结吧。除了必要的前端技术(HTML、Class、JavaScript等),还包括一些数据库技术(Mybatics、MySql、Memcached等)。框架用的spring boot,前端模板用的是Thymeleaf。
  目前这个网站还比较丑,没来得时做美化,基本思路是这样的。在最开始的登录界面,用户可以选择自己的身份,进行登录,有三种身份可供选择:

  • 普通用户:就是我们理解的顾客,来买东西的。
  • 管理员:这个相当于后台管理,有比较高的权限,可以管理所有的货物,用户信息,相当于店家和网站负责人双重身份。
  • 快递员:可以接单,配送等。

  有了这些身份,我们就可以以不同的身份登录,完成一个从购物到配送成功的全过程。其实没啥工作量,虽然我断断续续做了很长时间,最后发现,真正的难点居然都集中在前端了,排版确实是个费时费力的活,我只做了个登录界面,就累得不行。
  好了,不扯没用的,主要说说解决的一些问题。

1、Thymeleaf中文乱码问题:一定要在application.properties文件中配置thymeleaf的编码为UTF-8,只是把HTML5设为UTF-8和后端都设为UTF-8是没有用的,其实是个很简单的问题,却浪费了我很长时间。

#thymeleaf start
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html

2、密码存储:三种用户登录都需要用户名和密码,我对密码采用的是SHA加密算法,然后将其存入数据库中,字段类型为char(32),这种方式可以保证用户不会因为数据库遭到入侵而造成密码泄露。现在很多人都习惯注册很多网站都用相同的用户名和密码,如果其中一个网站采用明文存储,一旦泄露就会危及其他所有注册网站的安全。当年闹得沸沸扬扬的CSDN明文存储密码,造成用户数据泄露事件,至今我都难以理解,其实加密特别简单,都有封装好的函数可以用,问题是采用什么加密算法比较好呢?这个问题我决定等我读完密码学再来回答,实际上我还没开始读。
3、mybatis配置问题,不得不说使用spring boot实在是太方便了,首先引入依赖。

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.1.1</version>
</dependency>

  引入依赖后,需要在application.properties文件中配置一些设置。主要就是连接的数据库地址,还有用户名密码之类的。PS:我只是做个实验,请不要吐槽我的密码。

#mybatis
spring.datasource.url=jdbc:mysql://localhost:3306/goushuang
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

  有了以上的准备工作,就可以通过mapper操作我们的数据库了。直接贴代码举例吧:

@Mapper
@Component
public interface BookMapper {
    @Select("select id, name, num, price, classify, info from book where classify = #{classify}")
    List<Book> findByClassify(@Param("classify")String classify);

    @Select("select id, name, num, price, classify, info from book")
    List<Book> findAllBooks();

    @Select("select id, name, num, price, classify, info from book where name = #{name}")
    Book findBookByName(@Param("name")String name);

    @Update("update book set num = #{num} where name = #{name}")
    void updateBookNumByName(@Param("num")int num, @Param("name")String name);
}

  声明一个接口叫做BookMapper,和你想的一样,这个接口包括book表的所有操作。我的购物网站暂时只卖书,谢谢。然后将这个接口通过Component声明为Bean,当然还要声明为Mapper。对于查询(select)、更新(update)和删除(delete)操作,我们需要用不同的标签进行区分,例如@Select,后面括号里是查询语句,需要传入的参数用#{}括起来,然后在函数里进行传入。注意,在函数里进行传入的时候,需要用@Param标签指定传入的是哪个参数。也就是括号中的字符串要和查询语句中需要传入的参数名保持一致。
  下面就是具体的操作数据库了。还是先看例子吧。

@Autowired
private SystemOrderMapper systemOrderMapper;
...

public boolean acceptOrder(int id, String courier) throws RuntimeException{
    SystemOrder systemOrder = systemOrderMapper.selectOrderById(id);
    if(!systemOrder.getState().equals(OrderState.paid.getDescription())) {
        return false;
    }
    systemOrderMapper.updateStateAndCourierById(OrderState.deliver.getDescription(), courier, id);
    return true;
}

  这个函数用于快递员接单,我们将定义的mapper通过@autowired注解完成自动注入,然后直接使用就行了,传入订单id就可以查询订单状态,如果订单是有效的并且还没有被接单,快递员就可以完成接单了。 有个需要注意的地方时,我们首先通过订单id查询订单,让后修改了订单状态,为了保持一致性,我们要开启事务管理。可以将函数注解为@Transaction来开启事务管理,因为我已经将整个类注解了@Transaction,所以这里不需要再在函数上注解了。如果发生运行时错误,函数会抛出RuntimeException,操作会自动回滚。注意一定是运行时错误才可以,同时保证涉及到的表存储引擎都是InnoDB。如果想要使用事务,还需要在配置类上注解@EnableTransactionManagement。
4、一定不要用float或者double存储余额。我居然犯了这么低级的错误,用float存储了用户的余额,其结果就是有的时候莫名其妙少钱,对账都对不上。这个问题太低级就不谈了,java可以用高精度库或者直接存整数分就可以了。
5、如何生成订单id,这个问题很有趣,我特意去查了查,发现知乎的一个答案不错我直接贴原文了,如果侵权。。。你也不知道,我的博客又没人看。

作者:幂恩 链接:https://www.zhihu.com/question/19805896/answer/89087529 来源:知乎 著作权归作者所有,转载请联系作者获得授权。

你是个程序员。

隔壁老王通过你老婆找到你,说要做个”巨牛逼电商网站”,并许诺给你股份若干,你想想首付也攒了好久,就差200万就够了,于是就同意了,你花了一个星期做了一个网站并上线运营,订单号格式如下:

日期+6位自增数字

例如:

20160301000001 20160301000002 20160301000003 20160301000004 …

你很开心,这天你加班改完bug回家,老王从你的衣柜里跳出来对你说,不行啊大兄弟,为什么对门老张的”超屌电商网站”每天都知道我们有多少订单量呢?

没办法,你只能回去继续修改代码。

这就是最基本的流水号的问题,不仅仅会暴露你的交易量,而且有规律的订单号很容易成为安全隐患。

你又把订单号改为即时生成‘日期+6位随机数字’,并且也做了重复检查,心想这回应该没问题了吧,运营了一周之后,半夜里老王又从你的床底下爬出来说,不行啊大兄弟,为什么每天晚上下单都很慢/下单失败(取决于失误的大小)呢?。

没办法,你只能回去继续修改代码。

这就是即时随机数的问题,不仅仅是检测重复的性能差,你想一下一共六位数字理论值100万条,假设当天下单记录已有80w,接下来再下单可能会不断的随机并且产生的随机数都已经存在,而且,这种方式并发如果处理不好就会导致下单失败(数据库unique)或者相同订单号(数据库非unique)。

你苦思冥想,终于想到了解决办法,我每天把明天要用的订单号先随机好,放进redis之类的缓存里里随用随取,这样就不会有性能和并发的问题了,回家发现老婆不在家,于是你开心的玩起了dota。

这里已经很接近订单池的概念了,不过因为这个池子没有流动性,就让我们暂且叫做订单桶吧,每天都要往桶里打水。

随着用户量的增长,你们决定在三月三号做一个”对3促销节”,你在办公室监视着服务器,突然老王用你家座机给你打电话,大兄弟你快看下,下不了单了,你熟练的连接上服务器查找着问题,发现生成的订单号已经被用完了,这一天的促销不得不停止。

于是你又连续加班了三个月,做了一个实时监控订单号熟练的系统,当低于xxxxx的时候迅速生成新的订单号,并且买了更多的服务器,做了更多的集群,可以同时预留出更多的订单号等等等等。

这就是现在订单池的概念,随着订单号的被消费还继续生成着订单号,这个涉及的内容就很复杂了。

我讲这个故事不是想说隔壁老王跟你老婆的关系,也不是房价到底有多贵,创业公司到底怎么样,而是软件开发往往不是一蹴而就的,所有的东西都是不断进化的,你不可能起步就按照京东淘宝的标准来,根据实际情况实际分析就可以了,脱离需求谈实现都是耍流氓,比如就是一个内部的ERP,用户不超过200,每天生成订单量不超过50,用自增有问题么?我觉得没问题啊。你说呢。 引文完

5、未完待续。。。