二手商城系统分析与设计

cover

1.业务现状调查

1.1 业务背景

  • 简介
    ​ 天下苦闲鱼久矣。”你在闲鱼上出售闲置时遇到过最恶心的买家是什么样的?“、”你为什么放弃使用闲鱼?“,这些都是知乎上的热点问题。国内类似于闲鱼这样的二手交易平台鱼龙混杂,没有专门针对大学生这一群体。

    ​ 在大学校园里存在着很多的二手商品,由于信息资源的不流通以及传统二手商品信息交流方式的笨拙,导致了很多仍然具有一定价值或者具有非常价值的二手商品的囤积,乃至被当作废弃物处理。目前广东工业大学完成的二手物品交易九成以上都在微信群中完成,即卖家在微信群发布商品信息,买家联系卖家达成交易,交易完成后卖家发送”已出“信息表明货品已卖出;不足一成为熟人间转让。因此,我计划打造一个面向广东工业大学学生的高质量、高信任度的二手交易信息整合平台,将线上微信群二手改造成更加高效、便捷的网站线上二手交易。

  • 目标顾客

    ​ 广东工业大学学生及教师。大学生有很大的交易二手物品的需求,在开学、放假以及毕业时流动范围、流动频率很高,这就意味着有大量不再需要的物品产生,因此这个群体亟需一个高信任度、 高质量的二手交易平台。

  • 政策背景

    ​ 近年来分享经济的理念渗透、年轻一代二手交易主力的崛起,还有绿色消费的政策助力,二手交易的社会消费理念即将成为主流,互联网巨头纷纷进场:阿里闲鱼、58转转等,然而校园内却没有类似于这些巨头一样的产品,这些产品在校园内难免水土不服,因此校内市场仍未得到很好地开发。

1.2 业务概况

  • 岗位设置
      ![Snipaste_2020-05-03_15-16-37](https://myblog-figure-bed-1258528016.file.myqcloud.com/PicGo/20200503151654.png)
    
  • 主要工作

    总经理:负责整个项目的策划、组织、安排、实施、监控和制约;

    市场营销部:负责商品营销、校区联系,与校内各社团机构合作进行推广,吸引买家与卖家;

    财务部:负责财务的收支管理;

    用户服务部:负责调查用户满意度、解决售后问题。

    人事部:人员变动、系统授权;

  • 存在问题

    ​ 闲鱼创始人曾经谈二手交易的壁垒和盈利问题:“这个壁垒很高,比做一个京东的壁垒高多了。今天你开店做个小生意,货从哪里来?京东可以找商家或厂商去进货,但闲鱼的货来自每一个消费者,必须发动上亿消费者把家里的闲置拿出来卖,这是一个巨大的门槛。交易的过程中,又有各种不确定、不标准充斥交易环节,这意味着要付出巨大的成本才能促成一笔交易,如果没有非常强大的基础建设、交易技术、支付能力、信用产品支撑,没有大量服务创新、交易纠纷解决方案、人工智能和机制设置能力,这个事根本就玩不转。”。从中我们可以大致了解二手交易的四个问题:如何打破用户顾虑,解决交易信任难题、供需匹配难题、配套服务薄弱和平台盈利难。

1.3 业务目标

“让大学生没有难做的生意”。本系统将将聚集大量二手信息,使得无论是买还是卖都更加方便快捷高效,同时达到盈利能够支付运营成本的目标。

本系统核心目标是卖家发布商品信息、买家查询商品信息并发起预约请求,与卖家达成线下交易。为更好的完成该核心功能,需要同时实现其他辅助功能,如成交量查询、人员变动授权、售后调查问卷统计等

1.4 可行性分析

面对整个市场,如何打破用户顾虑,解决交易信任难题供需匹配难题配套服务薄弱平台盈利难这四个问题要复杂困难得多。但是面对大学生市场,这些问题就有了解决的可能。

首先,用户都是大学生群体。只要把握好身份审核,尽可能与学校达成合作,将有不诚信行为的学生名单反馈给学校等措施,就能够有效解决交易信任难题。

其次,大学生正在逐渐成为不可忽视的一股消费力量,这也意味着大学生有着大量二手物品或者对二手物品的需求,大学生拥有的闲置物品一直都存在释放的需求。

配套服务薄弱,比如物流服务。因此本系统是一个信息整合平台,让买家与卖家线下交易。

如何实现盈利?这是本项目最关键的部分。从二手交易平台的不同类型来看,交易撮合和寄售平台普遍的盈利模式是收取交易佣金,依据不同行业,佣金比例不同。但仅仅靠这个无法或者很难满足营销投入、人员检测、销售人员等成本。因此更多的需要将二手交易衍深其他服务,如二手+社交、二手+售后、二手+消费金融。二手交易常见的盈利方向就是二手+消费金融,因此我们可以为二手交易提供保险、分期付款等服务;二手交易同时也具有电商的基因,因此在后期也可以发展一手交易,进入一手电商市场,如新货买卖分享使用权等新模式。

2 业务需求分析

2.1 涉众分析

2.1.1 涉众概要

编号 涉众名称 涉众说明 期望
CMS001 买家 买家是二手商城系统的主要客户之一,且应当是通过认证的广东工业大学学生。买家在本系统需要收藏商品、预约线下购买商品,必要时请求客服帮助。 【1】预约商品;
【2】管理收藏商品【3】请求客服帮助
CMS002 卖家 卖家是二手商城系统的主要客户之一,且应当是通过认证的广东工业大学学生。卖家在本系统出售自己多余的二手物品,必要时请求客服帮助。 【1】出售商品
【2】管理在售商品【3】请求客服帮助
CMS003 财务部 结算一段周期内的成交量等数据,每月做财务报表。调整、发放每位员工的工资。 【1】通过计算机生成本月财务报表
【2】调整员工工资
CMS004 人事部 管理本系统除卖家和买家之外的人员变动、授权。负责招聘或者裁员。 【1】录入人员信息并授权与收回各部门人员授权(即招聘与裁员)
CMS005 市场营销部 组织发布活动,如毕业季、开学季购物节等活动。与卖家联系,将商品加入活动区域。 【1】发布活动;
【2】管理活动区商品【3】审核卖家加入活动区申请
CMS006 用户服务部 处理买家与卖家之间的纠纷,调查买家与卖家的满意率并每月做报表。 【1】线上调查用户满意率并导出报表
【2】提供客服支持
CMS007 学校 学校提供学生认证功能并处理不诚信学生。 【1】获取有不诚信行为的学生名单
【2】验证学生认证
CMS008 银行 支持线上支付 【1】提供线上支付

2.1.2 涉众简档

涉众 CMS001 买家
涉众代表 用户服务部代表买家提出期望
特点 系统的预期使用者,不可预计计算机应用水平的使用者
职责 【1】收藏商品
【2】向卖家提出线下购买商品申请
【3】向卖家提出取消购买商品申请,并提交取消理由
【4】向用户服务部提交请求帮助申请
【5】向用户服务部提交使用系统反馈
【6】查询以往订单信息
成功标准 【1】按要求正确购买、取消购买商品
【2】按要求正确向用户服务部门提交反馈
参与 不参与系统建设
可交付工件
意见/问题
涉众 SHM002 卖家
涉众代表 用户服务部代表卖家提出期望
特点 系统的预期使用者,不可预计计算机应用水平的使用者
职责 【1】发布商品
【2】在规定时间内通过或拒绝买家线下购买商品申请
【3】更改商品状态为在售、下架、上架以及商品价格描述等基本信息
【4】向用户服务部提交请求帮助申请
【5】向用户服务部提交使用系统反馈
【6】查询以往订单信息
成功标准 【1】按要求正确发布、下架商品
【2】按要求正确向用户服务部门提交反馈
【3】按要求正确修改商品描述
参与 不参与系统建设
可交付工件
意见/问题
涉众 CMS003 财务部
涉众代表 财务部部长×××
特点 系统的主要使用者,应具备相应的计算机操作水平,可培训
职责 【1】通过系统导出一段周期的财务报表
【2】修改、发放每位员工的工资
成功标准 【1】在规定时间内制作财务报表
【2】按照要求正确修改、发放员工的工资
参与 不参与系统建设
可交付工件 《×月财务报表》
意见/问题
涉众 CMS004 人事部
涉众代表 人事部部长×××
特点 系统的主要使用者,应具备相应的计算机操作水平,可培训
职责 【1】招聘新员工并录入信息
【2】裁员并更新员工信息
成功标准 【1】按要求正确录入、修改员工信息
参与 不参与系统建设
可交付工件 《×月招聘名单》《×月裁员名单》
意见/问题
涉众 CMS005 市场营销部
涉众代表 市场营销部部长×××
特点 系统的主要使用者,应具备相应的计算机操作水平,可培训
职责 【1】发布新促销活动
【2】管理促销区商品
成功标准 【1】按要求发布活动
【2】审核卖家加入活动申请
参与 不参与系统建设
可交付工件 《促销活动申请表》《关于商家参加×××促销活动的规则说明》
意见/问题
涉众 CMS005 用户服务部
涉众代表 用户服务部×××
特点 系统的主要使用者,应具备相应的计算机操作水平,可培训
职责 【1】收集用户评价反馈
【2】处理买家和卖家的客服帮助申请
【3】处理卖家与买家的纠纷
成功标准 【1】按月发布用户反馈报表
【2】提供客服帮助
【3】将有不诚信行为的学生名单上报学校
参与 不参与系统建设
可交付工件 《×月用户满意率报表》
意见/问题
涉众 SHM005 学校
涉众代表 学校教务系统负责人×××
特点 系统的非预期使用者,仅将不诚信学生名单的纸质文件上报给学校;
提供学生认证接口无需学校使用本系统。
职责 【1】提供学生认证接口
【2】接收不诚信学生名单文件
成功标准 【1】能够进行学生认证
【2】获取到不诚信学生名单并作出处理
参与 不参与系统建设
可交付工件
意见/问题
涉众 SHM008 银行
涉众代表 微信支付
特点 系统的非预期使用者
职责 【1】提供在线支付接口
成功标准 【1】能够正常在线支付
参与 不参与系统建设
可交付工件
意见/问题

2.2 业务边界

本报告准备实现以下两个业务:用户购买商品业务卖家出售商品业务

  • 用户购买商品业务

    业务范围:买家浏览商品、买家支付、买家确认收货

    业务主角:买家、银行

    业务目标:使得买家能够快速、便捷的购买商品

    边界:“买家购买商品服务”

    Snipaste_2020-05-06_12-55-49

    边界内部的业务工人的需求可以暂时忽略,而首先专注于满足买家和银行的需求,即购买商品、支付。

  • 卖家出售商品业务

    业务范围:卖家发布商品、卖家修改商品、卖家提现、卖家下架商品

    业务主角:卖家、银行

    业务目标:使得卖家能够快速、便捷的出售商品

    边界:卖家出售商品服务

    Snipaste_2020-05-06_12-56-37

    同样边界内部的业务工人的需求暂时忽略,专注于满足卖家和银行的需求,即出售商品、支付。

2.3 业务用例分析

2.3.1 获取业务用例

  • 较大的粒度获取业务用例

Snipaste_2020-05-06_11-54-21

  • 详细的业务用例

    Snipaste_2020-05-06_20-31-52

2.3.2 业务用例场景实现

  • 活动图

    • 买家活动图

      Snipaste_2020-05-06_16-06-55

    • 卖家活动图

      Snipaste_2020-05-06_16-10-52

  • 时序图

    Snipaste_2020-05-06_16-41-20

2.3.3 业务用例规约

用例名称 购买商品
用例描述 买家注册登录后,浏览商品并下单,线下取货
执行者 买家
前置条件 1. 注册并登录
2. 学生认证审核通过
后置条件 确认收货或者取消订单
主过程描述 1. 用户填写个人信息注册,请求学生认证接口。认证成功执行2,认证失败执行异常过程2.1
2. 买家浏览商品,与卖家交谈,谈拢成交执行3,未成交用例结束。
3. 线上下单,将订单信息提交给卖家。
4. 线下交易,买家满意则确认收货,同时执行分支过程4.1。买家不满意则取消订单。
5. 服务评价
分支过程描述 4.1 订单信息提交给财务部门,用于制作财务报表
异常过程描述 2.1 学生认证未通过,用例结束。
业务规则 买家必须为注册用户,即必须通过学生认证
设计的业务实体 买家档案,订单信息
用例名称 出售商品
用例描述 卖家注册登录后,发布商品,等待买家购买,线下发货
执行者 卖家
前置条件 1. 注册并登录
2. 学生认证审核通过
3. 发布了有效的商品
后置条件 商品售出或者订单被取消
主过程描述 1. 用户填写个人信息注册,请求学生认证接口。认证成功成为卖家后执行2,认证失败执行异常过程2.1
2. 卖家发布商品,等待买家咨询。
3. 买家咨询,如果双方均同意则由买家提交订单,线下交易;如果有一方或者两方不满意则用例结束。
4. 线下见面交易,买家满意则确认收货,并同时进行分支过程4.1。买家不满意则取消订单。
5. 卖家对服务进行评价
分支过程描述 4.1 订单信息提交给财务部门,用于制作财务报表
异常过程描述 2.1 学生认证未通过,用例结束。
业务规则 卖家必须为注册用户,即必须通过学生认证
设计的业务实体 卖家档案,订单信息

2.3.4 业务对象交互模型

用户档案与多个业务部门有关系,这些业务部门关心的和处理的数据又各有不同,因此有必要建立一个用户档案的模型,描述清楚用户档案的构成,以及档案各部分与各业务部门之间的存取关系。

  • 用户注册时填写必需信息,从而建立用户基本档案
  • 用户服务部门可能会修正用户的资料,比如审核学生认证后改变用户资料。

订单信息也与多个业务部门有关系,这些业务部门关心的和处理的数据也不同,因此同样有必要建立一个订单信息的模型。

  • 用户下单时,建立订单基本档案
  • 当订单状态改变,如用户取消订单或者确认收货,亦或是卖家取消订单,都会造成订单状态的改变,为订单添加了其他资料。

从以上分析中提取出用户基本资料、用户其他资料、订单基本资料、订单其他资料等领域对象

用户档案领域模型

订单资料领域模型

2.3.5 业务规则

  • 全局规则
编号 名称 描述 标志 日期 备注
001 安全性要求 本系统的所有用户都必须是广东工业大学师生 创建 2020.05
002 安全性要求 本系统的所有操作都必须登录 创建 2020.05
002.1 安全性要求 本系统的所有操作都必须登录,但是发布在主页上的系统公告可以被匿名用户浏览 创建 2020.05
  • 交互规则

    在前文的前置条件和后置条件中已经写出

  • 内禀规则

    实体名称 用户资料
    实体描述 用户档案中的基本资料,通过他能识别系统使用者的姓名、联系方式等
    属性名称 类型 精度 说明
    用户编号 字符 20 地区编号(6位)+学号(10位)+流水号(4位)
    联系方式 字符 11 手机号

    实体名称 订单资料
    ——– ————————————————
    实体描述 订单的基本资料,通过它能够得知订单价格、商品ID等
    属性名称 类型 精度 说明
    ——– —- —- —————————————–
    订单编号 字符 20 地区编号(6位)+日期(8位)+流水号(6位)

    实体名称 商品资料
    ——– ——————————————–
    实体描述 商品的基本资料,通过它能够得知商品的基本情况
    属性名称 类型 精度 说明
    ——– —- —- ——————————————
    商品编号 字符 30 用户编号(20位)+年份(4位)+流水号(6位)

3 概念模型构建

基于上述主要业务用例进一步抽象出概念用例,绘制概念用例场景图, 找到关键类对象,构建概念模型,明晰对象间关系和交互场景。本部分必须包括概念用例图、分析类视图、分析类场景图,可采用活动图、时序图、协作图等来展示。

3.1 概念用例分析

二手商城系统比较复杂,但是其最主要最核心的业务是发布商品、出售商品、购买商品,这就是撑起二手信息聚合平台业务的主线,几乎所有的业务用例都围绕这条主线展开。

因此提炼出以下关键业务:Snipaste_2020-05-06_20-36-59

3.2 分析概念用例

关键业务用例挑选出来之后,根据业务主线的需要,为这些业务用例找出概念用例。

Snipaste_2020-05-06_21-23-02

3.3 分析类场景图

  • 买家购买商品用例

Snipaste_2020-05-06_21-09-12

  • 卖家出售商品用例

    Snipaste_2020-05-06_21-35-18

3.4 分析类对象交互模型

交互时序图

4 系统分析

4.1 系统边界与系统用户

4.1.1 系统边界

由于在没有引入计算机系统之前的业务就是在微信群线上完成,分析原来的业务用例后可以发现,边界没有改变。原先所有的业务在引入计算机系统后都可以实现。

4.1.2 用户分析

用户是不同于涉众的抽象概念,一般是涉众的代表,是实实在在参与系统且需要编程实现的。

  • 用户概要
用户名称 用户概况和特点 使用系统方式 代表涉众
买家 买家是二手商城系统的主要客户之一,
且是通过认证的广东工业大学学生。
【1】注册、登录系统
【2】修改个人信息
【3】浏览商品;
【4】下单商品
【5】管理收藏商品
【6】请求客服帮助
买家
卖家 卖家是二手商城系统的主要客户之一,
且是通过认证的广东工业大学学生。
【1】注册、登录系统
【2】修改个人信息
【3】发布商品

【4】查看订单
【5】管理在售商品
【6】请求客服帮助
卖家
  • 用户简档
用户 买家
用户代表 管理学院某学生小王
说明 广东工业大学学生
特点 系统的预期使用者,不可预计计算机应用水平的使用者
职责 【1】注册登录系统
【2】编辑个人信息
【3】浏览商品
【4】向卖家提出线下购买商品申请
【5】向卖家提出取消购买商品申请,并提交取消理由
【6】向用户服务部提交请求帮助申请
【7】向用户服务部提交使用系统反馈
【8】查询以往订单信息
成功标准 【1】按要求正确购买、取消购买商品
【2】按要求正确向用户服务部门提交反馈
参与 页面设计
可交付工件 网站页面设计稿
意见/问题
用户 卖家
用户代表 管理学院学生小张
说明 广东工业大学学生
特点 系统的预期使用者,不可预计计算机应用水平的使用者
职责 【1】注册、登录系统
【2】发布商品
【3】在规定时间内通过或拒绝买家线下购买商品申请
【4】更改商品状态为在售、下架、上架以及商品价格描述等基本信息
【5】向用户服务部提交请求帮助申请
【6】向用户服务部提交使用系统反馈
【&】查询以往订单信息
成功标准 【1】按要求正确发布、下架商品
【2】按要求正确向用户服务部门提交反馈
【3】按要求正确修改商品描述
参与 页面设计
可交付工件 网站页面设计稿
意见/问题

4.2 系统用例分析

4.3 系统对象交互模型

系统对象交互模型

4.4 系统用例规约

用例名称 注册账号
用例描述 用户录入资料进行注册
执行者 买家或者卖家
前置条件 用户是广东工业大学学生
后置条件 用户成功注册并自动登陆系统
主事件描述 用户进入注册界面,输入基本信息,校验通过后即注册成功,并自动登录进入首页;校验失败进入异常事件1.1
分支事件描述
异常事件描述 1.1用户输入的信息校验不通过,如学号不正确、用户已注册过,不予通过注册,用例结束。
业务规则 a. 根据用户信息查看是否已经注册,若已经注册,则结束用例
b. 校验用户输入是否正确,不正确则结束用例
涉及的实体 用户档案
用例名称 登录账号
用例描述 用户输入登录凭证进行登录
执行者 买家或者卖家
前置条件 用户是广东工业大学学生且已经注册过,拥有本系统的登录凭证。
后置条件 用户成功登陆系统
主事件描述 用户进入登录界面,输入登录凭证,校验通过后即登录成功并跳转首页;校验失败进入异常事件1.1
分支事件描述
异常事件描述 1.1用户输入的登录凭证校验不通过,不予通过登录,用例结束。
业务规则 a. 判断登录凭证是否合法,合法则登陆成功,不合法给出提示
涉及的实体 用户档案
用例名称 浏览商品
用例描述 买家登录后浏览商品
执行者 买家
前置条件 买家已经登录
后置条件 买家能够查看商品
主事件描述 买家进入搜索商品界面,输入关键词,返回相关商品。如果未登录,进入异常事件1.1
分支事件描述
异常事件描述 1.1买家未登录,结束用例
业务规则 判断买家是否登录,如果已经登录则正常返回商品搜索结果,否则跳转登录界面,用例结束
涉及的实体 商品信息
用例名称 发布商品
用例描述 卖家登录后发布商品
执行者 卖家
前置条件 卖家已经成功登陆
后置条件 卖家成功发布商品信息
主事件描述 卖家进入后台管理界面,填写商品信息表后提交,成功发布商品。如果商家未登录,进入异常事件1.1
分支事件描述
异常事件描述 1.1卖家未登录,跳转登录界面,用例结束。
业务规则 判断卖家是否登录,如果已经登录,则正常发布商品流程;如果未登录,则跳转登录界面,结束用例。
业务规则 1.1卖家未登录,跳转到登录界面,结束用例
涉及的实体 商品信息
用例名称 下架商品
用例描述 卖家登录后下架已经发布的商品
执行者 卖家
前置条件 卖家已经成功登陆且商品已经成功发布
后置条件 卖家成功下架商品
主事件描述 卖家进入后台管理界面,查询商品信息,点击下架功能键下架商品。如果商家未登录,进入异常事件1.1
分支事件描述
异常事件描述 `1.1卖家未登录,跳转登录界面,用例结束。
业务规则 判断卖家是否登录,如果已经登录,则请求商品清单,然后处理卖家的下架请求;如果未登录,则跳转登录界面,结束用例。
业务规则 1.1卖家未登录,跳转到登录界面,结束用例
涉及的实体 商品信息
用例名称 下订单
用例描述 买家登录后下订单
执行者 买家
前置条件 买家已经成功登陆
后置条件 成功生成订单
主事件描述 买家进入商品详情页,点击下单,生成订单;如果未登录,进入异常流程1.1
分支事件描述
异常事件描述 1.1买家未登录,跳转登录页面,用例结束
业务规则 判断买家是否登录,如果已经登录,则正常下单;如果未登录,则跳转登录界面,结束用例。
涉及的实体 商品信息,订单信息
用例名称 支付商品费用
用例描述 买家登录后查询已经下过的单并支付
执行者 买家
前置条件 买家已经成功登陆且已经下过单
后置条件 成功支付
主事件描述 用户进入个人中心,查询到已经下过的单,点击立刻支付进行支付;如果未登录,进入异常流程1.1
分支事件描述
异常事件描述 1.1买家未登录,跳转登录页面,用例结束
业务规则 判断买家是否登录,如果已经登录,则正常查询订单、支付订单;如果未登录,则跳转登录界面,结束用例。
涉及的实体 订单信息
用例名称 查看订单
用例描述 卖家登录后查询买家下的订单
执行者 卖家
前置条件 卖家成功登陆
后置条件 查询订单成功
主事件描述 卖家进入个人中心,查询到买家下过的单;如果未登录,进入异常流程1.1
分支事件描述
异常事件描述 1.1卖家未登录,跳转登录页面,用例结束
业务规则 判断卖家是否登录,如果已经登录,则正常查询订单;如果未登录,则跳转登录界面,结束用例。
涉及的实体 订单信息
用例名称 请求客服帮助
用例描述 用户遇到问题后请求客服帮助
执行者 卖家或买家
前置条件
后置条件 问题被解决
主事件描述 用户在操作本系统时遇到无法解决的问题,寻求客服帮助
分支事件描述
异常事件描述
业务规则 无论有无登录,均可以联系在线客服寻求帮助
涉及的实体 工单信息

5 系统设计

5.1 系统架构与部署设计

建议采用四层架构:表示层、控制层、业务逻辑层和数据持久层

  • 登录、注册

Snipaste_2020-05-21_16-17-22

  • 订单管理:Snipaste_2020-05-21_17-26-31

  • 商品管理

    Snipaste_2020-05-21_18-15-34

5.2 包设计(项目目录结构)

Snipaste_2020-05-22_03-09-58

6 系统实现

6.1 系统开发环境及配置

Windows下开发,部署在CentOS 7服务器上

使用Spring Boot + Vue作为后端和前端框架

SSM环境的配置录了一个视频:哔哩哔哩

后期项目将会上传GIthub,因此具体配置文件不在此贴出

6.2 数据库实现

建立数据库gdutmall,字符编码为utf8mb4,排序规则为utf8mb4_unicode_ci。初步设计有四张表:

Snipaste_2020-05-22_04-46-12

  • 商品表Snipaste_2020-05-22_04-47-11

  • 用户表

    Snipaste_2020-05-22_04-48-04

  • 订单表

    Snipaste_2020-05-22_04-48-59

  • 商品图片资源表

    Snipaste_2020-05-22_04-49-26

6.3 主要模块核心代码实现

状态码枚举类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package xyz.swzdl.gdutmall.statuscode;

/**
* @author 盛文增
* @date 2020/8/29 20:30
*/
public enum StatusCodeEnum {
SUCCESS("0", ""),
SYSTEM_ERROR("1", "SYSTEM_ERROR"),
/**
* 1开头为用户相关问题
*/
ACCOUNT_NOT_REGISTER("1001", "用户未注册"),
ACCOUNT_ALREADY_REGISTERED("1002", "用户已注册"),
PASSWORD_NOT_CORRECT("1003", "密码错误"),
VERIFICATION_CODE_NOT_CORRECT("1004", "验证码错误"),
REGISTER_SUCCESS("1005", "注册成功"),
REGISTER_FAIL("1006", "注册失败"),
USER_NOT_LOGIN("1007","尚未登录"),
/**
* 2开头为商品问题
*/
COMMODITY_NOT_EXIST("2001","商品不存在");
/**
* 3开头为订单问题
*/
private final String code;
private final String desc;

StatusCodeEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}

public String getCode() {
return code;
}

public String getDesc() {
return desc;
}

@Override
public String toString() {
return "ErrorCodeEnum{" + "code='" + code + '\'' + ", desc='" + desc + '\'' + '}';
}
}

用户实体类(使用建造者模式):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
package xyz.swzdl.gdutmall.entity;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.io.Serializable;
import java.util.Collection;
import java.util.Date;

/**
* @author Sheng
*/
public class User implements Serializable, UserDetails {
/**
* 用户ID,此ID由数据库自动生成并递增
*/
@ApiModelProperty("用户ID,此ID由数据库自动生成并递增")
private Integer userId;
/**
* 用户登录密码
*/
@ApiModelProperty("用户登录密码")
private String userPassword;
/**
* 用户昵称
*/
@ApiModelProperty("用户昵称")
private String userNickName;

/**
* 真实姓名
*/
@ApiModelProperty("真实姓名")
private String userName;

/**
* 用户学号
*/
private String userStudentId;

/**
* 用户性别
*/
@ApiModelProperty("用户性别")
private Integer userGender;

/**
* 用户手机号
*/
@ApiModelProperty("用户手机号")
private String userPhone;

/**
* 用户邮箱
*/
@ApiModelProperty("用户手机号")
private String userMail;

/**
* 用户QQ
*/
@ApiModelProperty("用户QQ")
private String userQq;

/**
* 用户微信
*/
@ApiModelProperty("用户微信")
private String userWeChat;

/**
* 用户宿舍
*/
@ApiModelProperty("用户宿舍")
private String userDormitory;

/**
* 用户校区
*/
@ApiModelProperty("用户校区")
private String userCampus;

/**
* 用户注册时间
*/
@ApiModelProperty("用户注册时间")
private Date userRegisterTime;

/**
* 用户状态(如封号)
*/
@ApiModelProperty("用户状态(如封号)")
private Integer userStatus;

private static final long serialVersionUID = 1L;

@Override
public String toString() {
return new ObjectMapper().createObjectNode()
.put("userId",userId)
.put("userPassword",userPassword)
.put("userNickName",userNickName)
.put("userName",userName)
.put("userStudentId",userStudentId)
.put("userGender",userGender)
.put("userPhone",userPhone)
.put("userMail",userMail)
.put("userQq",userQq)
.put("userWeChat",userWeChat)
.put("userDormitory",userDormitory)
.put("userCampus",userCampus)
.put("userRegisterTime", String.valueOf(userRegisterTime))
.put("userStatus",userStatus)
.toString();
}

/**
* 需要提供空构造方法给MyBatis,不然MyBatis查询后返回此实体会报错
*/
public User() {
}

private User(Builder builder) {
this.userPassword = builder.userPassword;
this.userNickName = builder.userNickName;
this.userName = builder.userName;
this.userStudentId = builder.userStudentId;
this.userGender = builder.userGender;
this.userPhone = builder.userPhone;
this.userMail = builder.userMail;
this.userQq = builder.userQq;
this.userWeChat = builder.userWeChat;
this.userDormitory = builder.userDormitory;
this.userCampus = builder.userCampus;
this.userRegisterTime = builder.userRegisterTime;
this.userStatus = builder.userStatus;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}

@Override
public String getPassword() {
return this.userPassword;
}

@Override
public String getUsername() {
return this.userPhone;
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return this.userStatus == 0;
}

public static class Builder {
private final String userPassword;
private final String userNickName;
private String userName;
private String userStudentId;
private Integer userGender;
private final String userPhone;
private String userMail;
private String userQq;
private String userWeChat;
private String userDormitory;
private String userCampus;
private final Date userRegisterTime;
private final Integer userStatus;

public Builder(String userPassword, String userNickName, String userPhone, Date userRegisterTime, Integer userStatus) {
this.userPassword = userPassword;
this.userNickName = userNickName;
this.userPhone = userPhone;
this.userRegisterTime = userRegisterTime;
this.userStatus = userStatus;
}

public Builder userName(String userName) {
this.userName = userName;
return this;
}

public Builder userStudentId(String userStudentId) {
this.userStudentId = userStudentId;
return this;
}

public Builder userGender(int userGender) {
this.userGender = userGender;
return this;
}

public Builder userMail(String userMail) {
this.userMail = userMail;
return this;
}

public Builder userQq(String userQq) {
this.userQq = userQq;
return this;
}

public Builder userWeChat(String userWeChat) {
this.userWeChat = userWeChat;
return this;
}

public Builder userDormitory(String userDormitory) {
this.userDormitory = userDormitory;
return this;
}

public Builder userCampus(String userCampus) {
this.userCampus = userCampus;
return this;
}

public User build() {
return new User(this);
}
}

public Integer getUserId() {
return userId;
}

public String getUserPassword() {
return userPassword;
}

public String getUserNickName() {
return userNickName;
}

public String getUserName() {
return userName;
}

public String getUserStudentId() {
return userStudentId;
}

public Integer getUserGender() {
return userGender;
}

public String getUserPhone() {
return userPhone;
}

public String getUserMail() {
return userMail;
}

public String getUserQq() {
return userQq;
}

public String getUserWeChat() {
return userWeChat;
}

public String getUserDormitory() {
return userDormitory;
}

public String getUserCampus() {
return userCampus;
}

public Date getUserRegisterTime() {
return userRegisterTime;
}

public Integer getUserStatus() {
return userStatus;
}

public static long getSerialVersionUID() {
return serialVersionUID;
}
}

Spring Security配置类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package xyz.swzdl.gdutmall.configuration;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import xyz.swzdl.gdutmall.filter.CustomUsernamePasswordAuthenticationFilter;
import xyz.swzdl.gdutmall.service.serviceimpl.UserServiceImpl;
import xyz.swzdl.gdutmall.statuscode.StatusCodeEnum;

/**
* @author Sheng
*/
@Configuration
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

final UserServiceImpl userService;

public WebSecurityConfig(UserServiceImpl userService) {
this.userService = userService;
}

//@Bean
//PasswordEncoder passwordEncoder() {
// return new BCryptPasswordEncoder();
//}

@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAt(customUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
//只保护部分接口,其他接口一律放行
http.authorizeRequests()
//获取全部用户接口
.antMatchers("/api/v1/user/getAllUsers").authenticated().anyRequest().permitAll().and().formLogin()
//登录接口
.loginProcessingUrl("/api/v1/user/login")
//登录成功的返回
.successHandler((request, response, authentication) -> {
response.setContentType("application/json;charset=utf-8");
final var writer = response.getWriter();
writer.write(new ObjectMapper().writeValueAsString(authentication.getPrincipal()));
writer.close();
})
//登录失败的返回
.failureHandler((request, response, exception) -> {
response.setContentType("application/json;charset=utf-8");
final var writer = response.getWriter();
writer.write(new ObjectMapper().writeValueAsString(exception.getMessage()));
writer.close();
}).and().logout()
//注销接口
.logoutUrl("/api/v1/user/logout")
//注销成功的返回
.logoutSuccessHandler((request, response, authentication) -> {
response.setContentType("application/json;charset=utf-8");
final var writer = response.getWriter();
writer.write(new ObjectMapper().createObjectNode().put("errorCode", "200").put("errorMessage", "注销登录成功").toString());
writer.close();
}).permitAll().and()
.csrf().disable()
//访问未授权接口的返回
.exceptionHandling().authenticationEntryPoint((request, response, exception) -> {
response.setContentType("application/json;charset=utf-8");
final var writer = response.getWriter();
writer.write(new ObjectMapper().createObjectNode().put("errorCode", StatusCodeEnum.USER_NOT_LOGIN.getCode()).put("errorMessage", StatusCodeEnum.USER_NOT_LOGIN.getDesc()).toString());
writer.close();
});
}

/**
* 自定义的过滤器
*
* @return
* @throws Exception
*/
@Bean
CustomUsernamePasswordAuthenticationFilter customUsernamePasswordAuthenticationFilter() throws Exception {
CustomUsernamePasswordAuthenticationFilter filter = new CustomUsernamePasswordAuthenticationFilter();
filter.setAuthenticationSuccessHandler((request, response, authentication) -> {
response.setContentType("application/json;charset=utf-8");
final var writer = response.getWriter();
writer.write(new ObjectMapper().writeValueAsString(authentication.getPrincipal()));
writer.close();
});
filter.setAuthenticationFailureHandler((request, response, exception) -> {
response.setContentType("application/json;charset=utf-8");
final var writer = response.getWriter();
writer.write(new ObjectMapper().writeValueAsString(exception.getMessage()));
writer.close();
});
filter.setFilterProcessesUrl("/api/v1/user/login");
//重用WebSecurityConfigurerAdapter配置的AuthenticationManager,不然要自己组装AuthenticationManager
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}

@Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/js/**", "/css/**", "/img/**", "/fonts/**", "/index.html");
}
}

Druid等的配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#检查 MyBatis 配置文件是否存在,一般命名为 mybatis-config.xml
mybatis:
check-config-location: true
config-location: classpath:mybatis-config.xml
mapper-locations: classpath:mapper/*Mapper.xml

spring:
datasource:
druid:
url: jdbc:mysql://localhost:3306/gdutmall?serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
# 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
initial-size: 5
# 最大连接池数量
max-active: 8
# 已经不再使用,配置了也没效果
# maxIdle:
# 最小连接池数量
min-idle: 3
# 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
# max-wait:
# 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
pool-prepared-statements: false
# max-pool-prepared-statement-per-connection-size:
# max-open-prepared-statements:
# 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
validation-query: select 'x'
# 单位:秒,检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法
validation-query-timeout: 1
# 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
test-on-borrow: false
# 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
test-on-return: false
# 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
test-while-idle: true
# 有两个含义:
# 1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。
# 2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明
# time-between-eviction-runs-millis:
# min-evictable-idle-time-millis:
# max-evictable-idle-time-millis:

filters:
web-stat-filter:
enabled: true
url-pattern: /*
exclusions:
- '*.js'
- '*.gif'
- '*.jpg'
- '*.png'
- '*.css'
- '*.ico'
- /druid/*
session-stat-enable: true
session-stat-max-count: 1000
principal-session-name: Mr.Sheng
# principal-cookie-name:
profile-enable: true

stat-view-servlet:
enabled: true
url-pattern: /druid/*
# 允许清空数据
reset-enable: true
login-username: druid
login-password: druid
type: com.alibaba.druid.pool.DruidDataSource
server:
port: 8000
debug: false
logging:
level:
root: debug

日志配置(logback+slf4j):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?xml version="1.0" encoding="UTF-8" ?>

<configuration scan="true" scanPeriod="3 seconds">

<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/>

<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- <pattern>%relative [%thread] %level %logger - %msg%n</pattern>-->
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{32} - %msg%n</pattern>
</encoder>
<!-- <layout class="ch.qos.logback.classic.PatternLayout">-->
<!-- <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{32} - %msg%n</pattern>-->
<!-- </layout>-->
</appender>

<appender name="FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>log/logFile.log</File>
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>
log/logFile.%d{yyyy-MM-dd_HH-mm}.log.zip
</FileNamePattern>
</rollingPolicy>

<encoder>
<!-- <pattern>%relative [%thread] %level %logger - %msg%n</pattern>-->
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{32} - %msg%n</pattern>
</encoder>
<!-- <layout class="ch.qos.logback.classic.PatternLayout">-->
<!-- <Pattern>-->
<!-- %d{HH:mm:ss,SSS} [%thread] %-5level %logger{32} - %msg%n-->
<!-- </Pattern>-->
<!-- </layout>-->
</appender>

<root>
<level value="DEBUG"/>
<appender-ref ref="STDOUT"/>
<!-- <appender-ref ref="FILE"/>-->
</root>

<!--<include file="/tmp/logback-demo.xml"/>-->

</configuration>

Mybatis Generate配置自动生成Mapper类和Mapper的xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
<!DOCTYPE generatorConfiguration PUBLIC
"-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!-- 配置生成器 -->
<!-- 执行generator插件生成文件的命令: call mvn mybatis-generator:generate -e -->
<!-- 引入配置文件 -->
<properties resource="application.properties"/>

<context id="GDUTMall" targetRuntime="MyBatis3Simple">
<!--自动识别数据库关键字,默认为 false。如果设置为 true,就根据 SqlReservediords 中定义的关键字列表,一般保留默认值,遇到数据库关键字(Java关键字)时,使用 columnOverride 覆盖-->
<property name="autoDelimitKeywords" value="true"/>
<!--生成的Java文件的编码-->
<property name="javaFileEncoding" value="utf-8"/>
<!--beginningDelimiter和endingDelimiter:指明数据库中用于标记数据库对象名的符号,比如ORACLE就是双引号, MySQL默认是 (反引号) -->
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>
<!--格式化 Java 代码-->
<property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter"/>
<!--格式化 XML 代码-->
<property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>
<plugin type="org.mybatis.generator.plugins.SerializablePlugin"/>
<plugin type="org.mybatis.generator.plugins.ToStringPlugin"/>
<commentGenerator>
<!--是否取消注释-->
<property name="suppressAllComments" value="false"/>
<!--是否生成带时间戳注释-->
<property name="suppressDate" value="true"/>
</commentGenerator>

<jdbcConnection driverClass="${spring.datasource.driver}"
connectionURL="${spring.datasource.url}"
userId="${spring.datasource.username}"
password="${spring.datasource.password}"/>
<javaTypeResolver>
<property name="forceBigDecimals" value="true"/>
</javaTypeResolver>
<!--生成实体-->
<javaModelGenerator targetPackage="xyz.swzdl.gdutmall.entity" targetProject="src/main/java"/>
<!--生成mapper.xml-->
<sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
<property name="enableSubPackages" value="false"/>
</sqlMapGenerator>
<!--生成接口-->
<javaClientGenerator type="XMLMAPPER" targetPackage="xyz.swzdl.gdutmall.dao" targetProject="src/main/java">
<property name="enableSubPackages" value="false"/>
</javaClientGenerator>

<table tableName="user"
enableCountByExample="true"
enableUpdateByExample="true"
enableDeleteByExample="true"
enableSelectByExample="true"
enableInsert="true"
enableSelectByPrimaryKey="true"
enableDeleteByPrimaryKey="true"
enableUpdateByPrimaryKey="true">
<property name="useActualColumnNames" value="false"/>
</table>

<table tableName="commodity"
enableCountByExample="true"
enableUpdateByExample="true"
enableDeleteByExample="true"
enableSelectByExample="true"
enableInsert="true"
enableSelectByPrimaryKey="true"
enableDeleteByPrimaryKey="true"
enableUpdateByPrimaryKey="true">
<property name="useActualColumnNames" value="false"/>
</table>

<table tableName="image"
enableCountByExample="true"
enableUpdateByExample="true"
enableDeleteByExample="true"
enableSelectByExample="true"
enableInsert="true"
enableSelectByPrimaryKey="true"
enableDeleteByPrimaryKey="true"
enableUpdateByPrimaryKey="true">
<property name="useActualColumnNames" value="false"/>
</table>

<table tableName="order"
enableCountByExample="true"
enableUpdateByExample="true"
enableDeleteByExample="true"
enableSelectByExample="true"
enableInsert="true"
enableSelectByPrimaryKey="true"
enableDeleteByPrimaryKey="true"
enableUpdateByPrimaryKey="true">
<property name="useActualColumnNames" value="false"/>
</table>
</context>
</generatorConfiguration>

前端参数校验:

1
2
3
4
5
6
7
8
9
// 注册表单检验规则
registerCheckRules: {
// 不需要失焦验证,改为点击获取验证码按钮时进行验证
phoneNumber: [],
VerificationCode: [{ validator: validateCode, trigger: "blur" }],
name: [{ validator: validateName, trigger: "blur" }],
password: [{ validator: validatePassword, trigger: "blur" }],
checkPass: [{ validator: validatePass2, trigger: "blur" }]
},

用户名验证规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const validateName = (rule, value, callback) => {
const that = this;
if (value === "") {
return callback(new Error("请输入用户名!"));
} else {
that.$http
.post(
"/user/checkUserNicName",
{
userName: value
},
{ headers: { "Content-Type": "application/json" } }
)
.then(function(response) {
// console.log(response)
if (response.data.errorCode === 200) {
// that.disableGetVerificationCode = false
callback();
} else {
return callback(new Error(response.data.errorMessage));
}
})
.catch(function() {
return callback(new Error("检测用户名是否可用错误,请稍后重试!"));
// that.disableGetVerificationCode = false
// callback()
});
}
};

7 系统测试

主页(未登录):image-20200831014903409

关于页面:image-20200831014931982

登陆页面:image-20200831014957290

注册页面:image-20200831015016823

主页(已经登陆):image-20200831014452444

搜索结果页:image-20200831015056418

底部的分页处理:image-20200831015141437

Druid控制台:image-20200831020820867

API文档:image-20200831020850774

8 总结

  1. 在使用Spring Security的过程中遇到很多问题也学到了很多,比如Spring Security默认使用前后端不分离的形式,在修改其为前后端分离的时候,遇到POST传递登录名和登陆密码,但是Spring Security获取到null的问题:

    1
    2
    3
    11:22:08.505 [http-nio-8000-exec-10] DEBUG o.m.s.t.SpringManagedTransaction - JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@51d41223] will be managed by Spring

    11:22:08.505 [http-nio-8000-exec-10] DEBUG x.s.g.d.U.getUserByPhone - ==> Preparing: select user_id, user_password, user_nick_name, user_name, user_student_id, user_gender, user_phone, user_mail, user_qq, user_we_chat, user_dormitory, user_campus, user_register_time, user_status from user where user_phone = ?

    通过查阅源码找到获取用户名密码是UsernamePasswordAuthenticationFilter这个类,源码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
    throws AuthenticationException {
    if (postOnly && !request.getMethod().equals("POST")) {
    throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    }

    String username = obtainUsername(request);
    String password = obtainPassword(request);

    if (username == null) {
    username = "";
    }

    if (password == null) {
    password = "";
    }

    username = username.trim();

    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

    // Allow subclasses to set the "details" property
    setDetails(request, authRequest);

    return this.getAuthenticationManager().authenticate(authRequest);
    }

    而从request中获取username的方法obtainUsername为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /**
    * Enables subclasses to override the composition of the username, such as by
    * including additional values and a separator.
    *
    * @param request so that request attributes can be retrieved
    *
    * @return the username that will be presented in the <code>Authentication</code>
    * request token to the <code>AuthenticationManager</code>
    */
    @Nullable
    protected String obtainUsername(HttpServletRequest request) {
    return request.getParameter(usernameParameter);
    }

    发现这是从form表单中取值,但是使用axios将参数放在body则取不到值,因此可以重写获取用户名和密码的方法,自定义一个过滤器,继承UsernamePasswordAuthenticationFilter类,然后重写其获取用户名密码的方法,再在spring配置中进行注册该自定义过滤器,并将该过滤器注册到Spring Security的过滤器链中。

  1. Spring Security认证使用传统的cookie-session模式,当从单服务变为多服务时候,应当使用JSON Web Token (JWT)
  2. 小程序端未开发完成image-20200831020741895
  3. 对于一个完整的电商项目而言,数据库设计很糟糕。没有完善的考虑商品SKUSPU等的处理,距离真正落地可商用项目还很大。另外应当独立开发一个CMS,很多内容都是写死在程序里,后期如果需要更改就需要修改源代码,非常不合理,应该能够通过CMS进行修改。
  4. 短信验证码由于无法在腾讯云申请短信签名的缘故,因此无法发送验证码,暂时使用000000代替
  5. 没有设计统一异常处理

评论

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×