简易版电商系统实现记录

前言

最近手上没啥活就干脆把之前一直想练的一个项目拿出来做做,挺有意思的一点就是平时都是自己在别人搭好的项目结构上直接写业务逻辑,当自己从0开始写项目的时候还是遇到了一些问题,也算是更宏观的学习如何做出一个项目。

这个项目的角色有用户和管理员,主要涉及到的业务逻辑有用户管理、商品管理、购物车管理、订单管理和支付管理。实际应用中的电商项目肯定很复杂,除了基本的业务逻辑外,如果数据访问量比较大的话还会加redis缓存机制,如果需要离线计算还会加Hadoop框架...这个练手的项目本身是使用SSM框架做的maven项目,打算直接使用spring boot加mybatis,另外使用swagger测试。

准备

  1. MySQL
  2. Navicat
  3. IDEA
  4. Git

实现过程

数据库

用户表、商品类别表、商品表、购物车表、订单表、订单详情表、支付信息表和地址信息表

1. 用户表

用户名不能重复,所以单服务器中多并发的情况下可以使用同步加锁的方法查询数据库中是否有同名用户(多个用户同时注册);如果是多节点即架构为分布式的时候,锁失效,需要使用数据库的唯一索引unique_key,通过btree的方式将username设为唯一索引,如此一来对于username的唯一性判断就交给mysql,业务中就无须加锁/同步方法去校验用户名是否存在。

2. 类别表

同一个父类别下的类别按照sort_order大小顺序排列,如果sort_order值相同,则按照id大小排序。

3. 商品表

产品主图中存储的是url的相对地址,获取的时候可以从数据库获取这个图片的相对地址后叠加项目配置文件中的图片存储路径;后续如果项目有迁移,直接修改配置文件中的路径即可,数据库不用做修改。

商品详情中存储很多html样式,还有图片外链等信息

decimal(20,2)表示20位数中有两位小数,即小数点前18位,小数点后2位。对应Java中使用BigDecimal进行计算(需要考虑到计算中丢失精度的问题)

库容量stock字段的大小是11位(100 0000 0000),并非11个

4. 购物车表

由于购物车表经常会通过userId进行查询,所以使用btree创建一个关于user_id的普通索引(根据这个索引查询会比普通字段查询效率高),提高查询效率。

5. 支付信息表

6. 订单表

订单号加了个唯一索引,插入数据的时候保证订单号唯一,并且可用于提高查询效率(高频率通过订单号查询订单场景使用)

表中的payment字段表示商品付款金额,不需要与产品中的金额做联动处理(用户可以高于/低于原价购买商品。这个payment是用户购买的时候的真实付款金额)

对于订单状态的字段值设置,可以加上大小判断,例如如果一个订单状态是20,可以通过是否大于20判断这个订单是否以发货/交易成功/交易关闭(给状态字段加上其他处理,并非单纯的是否等于某个值)

支付时间是从支付宝回调的状态信息中的时间作为该订单支付的时间

交易关闭是指购买生成订单后长时间没有支付,导致订单失效,交易自动关闭的时间

7. 订单详情表

本身可以通过order_no然后通过支付信息表获取user_id,但是可以加上user_id这个冗余来提高查询效率(取消联表查询操作)

由于商品id对应的名称和图片后续会随着商家的修改而变动,所以订单明细中需要存储当前订单中产品的基本信息,以供用户后续自行查看(类似快照)

索引有两个,一个是order_no,另一个是user_id和order_no的组合索引(用于查询某个用户的某个订单信息)注意这两个索引均为普通索引,并没有唯一特性,所以在表中可以重复,主要用于提高查询效率

这里给出mysql表字段类型设计的时候的知识:

源自:https://www.cnblogs.com/yinqanne/p/9635106.html

源自:https://www.cnblogs.com/baizhanshi/p/8482068.html

从业务逻辑出发:

  1. 用户注册:在用户表中添加一个条目
  2. 用户登录:从用户表中查询是否有该用户,如果有且密码相同则登录成功进入系统
  3. 用户查询产品,涉及分类表和产品表,可以通过分类表的id查询某个类别下所有的产品
  4. 用户确定某个产品加入购入车,会在购物车信息表中添加一个条目(包含用户id、产品id、产品数量以及是否勾选的状态信息)。用户可以在购物车中看到自己添加到购物车的所有产品(展示购物车表中根据用户id所有的条目)
  5. 用户选择购买购物车中的某个条目,进入订单确认页,展示该用户所有的地址信息/给定一个设置好的默认地址信息,用户确认地址后生成一个订单信息插入到订单表中
  6. 订单表中包含用户id、订单id、地址id。金额通过产品id从产品表中获得(通过订单详情表与产品表一对一查询实现)。一个订单中可包含多个订单详情(订单详情表与订单表是多对一的关系),用户点击订单中的某个产品进入订单详情页(包括用户id、产品id、订单id、产品生成订单时的名称、图片、数量、总价等信息)
  7. 用户支付订单,由第三方产商生成流水号返回到订单表中,修改订单的流水信息和订单状态信息(未支付-已支付-订单取消等)

在设计数据表的时候需要注意的点:

  1. 在一般项目中不用外键,在后续数据量增大后外键对分库分表以及清洗数据操作不友好。触发器同理
  2. 考虑到后续访问量暴增的现象(推广/爆红),可以在表中添加冗余字段,减少表联查操作,提高查询效率(例如产品表中产品的图信息)

项目结构

一般一个JavaWeb项目基本都使用MVC架构,分为controller、entity、service、mapper层分别代表与前端交互的、pojo与数据库表映射的、业务端、持久层。

使用IDEA创建一个springboot项目,这里有一个技巧就是如果直接从IDEA中导入创建加载springboot项目会由于网慢导致失败,可以直接从https://start.spring.io/填写必要信息选择依赖,下载zip包到本地,直接解压导入即可~

使用MyBatis-Generator反向生成代码

1. 配置pom.xml依赖

            <plugin>
				<groupId>org.mybatis.generator</groupId>
				<artifactId>mybatis-generator-maven-plugin</artifactId>
				<version>1.3.2</version>
				<configuration>
					<!-- 在控制台打印执行日志 -->
					<verbose>true</verbose>
					<!-- 重复生成时会覆盖之前的文件 -->
					<overwrite>true</overwrite>
					<!-- mybatis配置文件路径 -->
					<configurationFile>src/main/resources/mybatis-generator.xml</configurationFile>
				</configuration>
				<dependencies>
					<dependency>
						<groupId>mysql</groupId>
						<artifactId>mysql-connector-java</artifactId>
						<version>8.0.17</version>
					</dependency>
				</dependencies>
			</plugin>

2. 编写数据库配置文件jdbc.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mall?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT
jdbc.username=root
jdbc.password=root

3. 编写反向生成mybatis代码的配置文件mybatis-generator.xml

配置文件引用头:http://mybatis.org/generator/configreference/xmlconfig.html

具体配置项讲解可参考:https://baijiahao.baidu.com/s?id=1659247254821180985&wfr=spider&for=pc

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>

    <!-- 导入数据库配置文件 -->
    <properties resource="jdbc.properties"></properties>

    <!-- context:逆向工程的主要配置信息
            属性:
                id:名称
                targetRuntime:设置生成的文件适用于哪个mybatis版本,默认MyBatis3-->
    <context id="study" targetRuntime="MyBatis3">
        <!-- 可选的,用于创建class的时候对注释进行控制 -->
        <commentGenerator>
            <!-- 去除指定生成的注释中是否包含生成的日期,true表示去除日期 -->
            <property name="suppressDate" value="true"/>
            <!-- 是否去除自动生成的注释,true表示去除 -->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <!-- 数据库JDBC连接 -->
        <jdbcConnection driverClass="${jdbc.driver}"
                        connectionURL="${jdbc.url}"
                        userId="${jdbc.username}"
                        password="${jdbc.password}">
            <property name="nullCatalogMeansCurrent" value="true"/>
        </jdbcConnection>
        <!-- 非必须的,类型处理器,在数据库类型和java类型之间的转换控制 -->
        <javaTypeResolver>
            <!-- 默认false,scale>0;length>18:使用BigDecimal;
                            scale=0;length[10,18]:使用Long;
                            scale=0;length[5,9]:使用Integer;
                            scale=0;length<5:使用Short;;true时把JDBC DECIMAL 和
      NUMERIC 类型解析为java.math.BigDecimal-->
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>
        <!-- 配置反向自动生成的实体类
                targetPackage:生成的实体类所在的包
                targetProject:生成的实体类所在的硬盘位置-->
        <javaModelGenerator targetPackage="com.practice.mall.domain" targetProject="src/main/java">
            <!-- 是否允许子包,false表示不允许-->
            <property name="enableSubPackages" value="false"/>
            <!-- 是否清理从数据库中查询出的字符串左右两边的空白字符,true是 -->
            <property name="trimStrings" value="true"/>
            <!-- 是否对modal添加构造函数 -->
<!--            <property name="constructorBased" value="true"/>-->
            <!-- 建立modal对象是否不可改变 即生成的modal对象不会有setter方法,只有构造方法 -->
<!--            <property name="immutable" value="false"/>-->
        </javaModelGenerator>
        <!-- 配置反向自动生成的mapper.xml文件-->
        <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
            <!-- 针对数据库的一个配置,是否把 schema 作为字包名 -->
            <property name="enableSubPackages" value="false"/>
        </sqlMapGenerator>
        <!-- 配置反向自动生成的mapper类 -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.practice.mall.mapper" targetProject="src/main/java">
            <!-- 针对 oracle 数据库的一个配置,是否把 schema 作为字包名 -->
            <property name="enableSubPackages" value="false"/>
        </javaClientGenerator>
        <!-- 配置生成的表 tableName是数据库中的表名,domainObjectName是生成的JAVA模型名
             tableName(必要):要生成对象的表名
        -->
        <table tableName="mall_user" domainObjectName="MallUser" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table>
        <table tableName="category" domainObjectName="Category" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table>
        <table tableName="product" domainObjectName="Product" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false">
            <columnOverride column="sub_images" javaType="java.lang.String" jdbcType="VARCHAR"></columnOverride>
            <columnOverride column="detail" javaType="java.lang.String" jdbcType="VARCHAR"></columnOverride>
        </table>
        <table tableName="cart" domainObjectName="Cart" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table>
        <table tableName="pay_info" domainObjectName="PayInfo" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table>
        <table tableName="mall_order" domainObjectName="MallOrder" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table>
        <table tableName="order_item" domainObjectName="OrderItem" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table>
        <table tableName="shipping" domainObjectName="Shipping" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table>
    </context>
</generatorConfiguration>

3. 利用mybatis-generator插件反向生成代码(domain实体类、mapper持久层类、mapper.xml MyBatis数据库CRUD代码)

至此,基本的CRUD操作已经都有了,后续根据业务需要会手动修改mapper文件对数据库进行操作。

配置Swagger-ui以及项目数据库连接池

1. 配置pom.xml

<!-- swagger -->
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<version>2.6.1</version>
		</dependency>
		<!-- swagger-ui -->
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>
			<version>2.6.1</version>
		</dependency>
		<!-- alibaba的druid数据库连接池 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>1.1.10</version>
		</dependency>

2. 编写项目配置文件application.yml/application.properties

#服务器端口和访问路径配置
server:
port: 8080
servlet:
context-path:

#数据库访问
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mall?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT
    username: root
    password: 123456
    druid:
      max-wait: 60000
      max-active: 10
      min-idle: 1
#配置mybatis(相当于普通MyBatis中的SqlMapConfig.xml主配置文件中指定映射配置文件的地址和别称)
mybatis:
  mapper-locations: classpath:/mapper/*.xml
  type-aliases-package: com.practice.mall.domain
  configuration:
    map-underscore-to-camel-case: true
    use-generated-keys: true
    cache-enabled: false

3. 编写SwaggerConfiguration类

@Configuration //容器的配置
@EnableSwagger2
public class SwaggerConfiguration {
    /**
     * 注册Bean实体
     * @return
     */
    @Bean //bean实体创建
    public Docket createRestApi(){

        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                //当前包名
                .apis(RequestHandlerSelectors.basePackage("com.practice.mall"))
                .paths(PathSelectors.any())
                .build();
    }

    /**
     * 构建API文档的详细信息方法
     * @return
     */
    public ApiInfo apiInfo(){
        return new ApiInfoBuilder()
                //API页面标题
                .title("Spring Boot实现电商系统")
                //创建者
                .contact(new Contact("ciery","https://mp.csdn.net/console/article",""))
                //版本
                .version("1.0")
                //描述
                .description("API描述")
                .build();
    }
}

4. 编写Controller层代码测试Swagger

@Api("用户管理API")
@RestController
public class UserController {
    @ApiOperation(value = "测试",notes = "测试swagger")
    @GetMapping("/test")
    public String test(@RequestParam("id") Long id){
        if(id==0){
            return "test failure";
        }
        return "test success";
    }
}

运行项目,在浏览器输入locahost:8080/swagger-ui.html进入swagger-ui界面

将项目部署到远程Git仓库中

实际项目开发中往往是多个人一起协作开发,所以git多版本代码管理工具就很派上用场,使用码云作为代码托管。在此简单记录部署代码到远程库中的过程。

1. 创建远程仓库

2. IDEA中创建本地git仓库并连接远程库

3. 先同步本地库和远程库,然后commit-push即可

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值