近年来,中国的电子商务快速发展,交易额连创新高,电子商务在各领域的应用不断拓展和深化、相关服务业蓬勃发展、支撑体系不断健全完善、创新的动力和能力 不断增强。电子商务正在与实体经济深度融合,进入规模性发展阶段,对经济社会生活的影响不断增大,正成为我国经济发展的新引擎。
中国电子商务研究中心数据显示,截止到2012年底,中国电子商务市场交易规模达7.85万亿人民币,同比增长30.83%。其中,B2B电子商务交易额 达6.25万亿,同比增长27%。而2011年全年,中国电子商务市场交易额达6万亿人民币,同比增长33%,占GDP比重上升到13%;2012年,电子商务占GDP的比重已经高达15%。2013年我国电子商务规模已突破十万亿大关。
电商行业技术特点-
技术新
-
技术范围广
-
分布式
-
高并发、集群、负载均衡、高可用
-
海量数据
-
业务复杂
-
系统安全
-
B2B:企业到企业,商家到商家。代表:阿里巴巴、慧聪网
-
B2C:商家到客户。代表:京东、淘宝商城(B2B2C)
-
C2C:客户到客户。淘宝集市
-
O2O:线上到线下
shop网上商城是一个综合性的B2C平台,类似京东商城、天猫商城。会员可以在商城浏览商品、下订单,以及参加各种活动。
管理员、运营可以在平台后台管理系统中管理商品、订单、会员等。客服可以在后台管理系统中处理用户的询问以及投诉。
功能描述后台管理系统:管理商品、订单、类目、商品规格属性、用户管理以及内容发布等功能。
前台系统:用户可以在前台系统中进行注册、登录、浏览商品、首页、下单等操作。
会员系统:用户可以在该系统中查询已下的订单、收藏的商品、我的优惠券、团购等信息。
订单系统:提供下单、查询订单、修改订单状态、定时处理订单。
搜索系统:提供商品的搜索功能。
单点登录系统:为多个系统之间提供用户登录凭证以及查询登录用户的信息。
技术架构随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。
-
单一应用架构
-
- 当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。
- 此时,用于简化增删改查工作量的 数据访问框架(ORM) 是关键。
- 缺点:随着应用功能的增多,代码量越来越大,越来越难维护,那怎么解决代码一体化的问题?
-
垂直应用架构
-
- 当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。
- 此时,用于加速前端页面开发的 Web框架(MVC) 是关键。
- 缺点:垂直架构中相同逻辑代码需要不断的复制,不能复用。每个垂直模块都相当于一个独立的系统。
-
分布式服务架构
-
- 当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。
- 此时,用于提高业务复用及整合的 分布式服务框架(RPC) 是关键。
- 缺点:服务越来越多,需要管理每个服务的地址,调用关系错综复杂,难以理清依赖关系,服务状态难以管理,无法根据服务情况动态管理。
-
流动计算架构
-
- 当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。
- 此时,用于提高机器利用率的 资源调度和治理中心(SOA) 是关键。
- 缺点:服务间会有依赖关系,一旦某个环节出错会影响较大,服务关系复杂,运维、测试部署困难,不符合DevOps思想。
-
微服务架构:
-
-
单一职责:微服务中每一个服务都对应唯一的业务能力,做到单一职责
-
微:微服务的服务拆分粒度很小,例如一个用户管理就可以作为一个服务。每个服务虽小,但“五脏俱全”。
-
面向服务:面向服务是说每个服务都要对外暴露服务接口API。并不关心服务的技术实现,做到与平台和语言无关,也不限定用什么技术实现,只要提供Rest的接口即可。
-
自治:自治是说服务间互相独立,互不干扰
-
- 团队独立:每个服务都是一个独立的开发团队,人数不能过多。
- 技术独立:因为是面向服务,提供Rest接口,使用什么技术没有别人干涉
- 前后端分离:采用前后端分离开发,提供统一Rest接口,后端不用再为PC、移动端开发不同接口
- 数据库分离:每个服务都使用自己的数据源
- 部署独立,服务间虽然有调用,但要做到服务重启不影响其它服务。有利于持续集成和持续交付。每个服务都是独立的组件,可复用,可替换,降低耦合,易维护 Docker部署服务
-
思考:有什么问题?
-
模块之间耦合度太高,其中一个升级其他都得升级
-
开发困难,各个团队开发最后都要整合一起
-
系统的扩展性差
-
不能灵活的进行分布式部署。
解决方法:系统拆分
优点:把模块拆分成独立的工程,单点运行。如果某一个点压力大可以对这一个点单独增加配置。其他的点不受影响。
分布式架构:把系统按照模块拆分成多个子系统。
优点:
-
把模块拆分,使用接口通信,降低模块之间的耦合度。
-
把项目拆分成若干个子项目,不同的团队负责不同的子项目。
-
增加功能时只需要再增加一个子项目,调用其他系统的接口就可以。
-
可以灵活的进行分布式部署。
缺点:
- 系统之间交互需要使用远程通信,接口开发增加工作量。
-
Java(核心编程语言)
-
SpringBoot、SpringMVC、Mybatis(三大框架)
-
Dubbo(分布式服务框架)
-
Zookeeper(服务注册中心)
-
Redis(缓存数据库)
-
Elasticseach(搜索引擎)
-
SSO(单点登录)
-
MySql(数据库)
-
Nginx(web服务器)
-
七牛云(文件上传服务器)
-
RabbitMQ(消息队列)
-
Alipay(支付宝支付)
-
腾讯验证码(验证)
-
jQuery、Bootstrap(前端框架)
-
doT.js(模板引擎)
-
UEditor(富文本编辑器)
-
Google Kaptcha(图形验证码)
-
IntelliJ IDEA
-
Maven 3.6.2
-
JDK 1.8.0_231
-
Tomcat 9.0.29
-
MySql 5.7.20
-
Nginx 1.16.1
-
Dubbo 2.6.0
-
Zookeeper 3.4.13
-
Redis 5.0.3
-
Elasticsearch 7.4.2
-
Win10(开发环境)
-
Centos7.3-1908(部署环境)
说明:单独使用可以随意选择版本,如果在集群下需要考虑多种技术之间的兼容性。比如:Redis、Elasticsearch可以使用单台服务器也可以使用多台服务器,使用多台服务器时需要考虑与其他技术的兼容性,或者统一都用高版本jar包,或者统一降低版本jar包,一高一低可能会出问题。
人员配置-
产品经理:3人,确定需求以及给出产品原型图。
-
项目经理:1人,项目管理。
-
架构师:2人,负责解决技术难点与定制标准(开发准则、接口标准、技术选型等)。
-
前端团队:5人,根据产品经理给出的原型制作静态页面。
-
安卓团队:5人,开发android客户端。
-
IOS团队:5人,开发IOS客户端。
-
后端团队:20人,实现整个系统业务功能。
-
测试团队:5人,测试所有的功能、性能、安装。
-
运维团队:3人,项目的发布以及维护。
-
如果采用oracle,需要配备DBA:2人。
- 节约成本
包括硬件成本和软件成本,硬件包括店面、房租、装修、印刷、纸张等最必须用品,软件包括网上商城购物系统、网络信息、图片、视屏等,都可长期使用、良性循环、经济和环保。
- 营销推广经济、便捷
传统媒体广告费用高昂,更适合于进行品牌塑造;而网络营销主要是策略与定位把控的问题,实惠很多,费用与传统媒体相比微乎其微,并且流量与用户也更加精准。
- 信息更加立体、全面
通过互联网,企业的信息展示、品牌塑造和形象宣传可以通过文字、图片、音频、视频等多维度进行现实与虚拟相结合的展示,使用户对企业的了解更加立体和全面,有助于形成良好的形象与口碑。
- 管理高效、便捷
运用信息化的数据库管理,各类信息精准、清晰、无误的保存,避免出现人工操作出现低级错误的情况,可随时查阅、核算、统计。
功能点- 商家入驻
商家入驻功能包括:商家入驻申请流程、商家店铺自定义装修功能、多套店铺模板选择、商家店铺街展示、商家独立店铺功能、商铺报表统计功能、搜索店铺列表页、商家自定义广告位、区分平台与商家分类、类型、订单分单功能(按商家)、订单退换功能、商家订单佣金结算等。
- 会员中心
会员中心功能包括:会员中个人主页美化、会员中心订单列表美化、会员中心收货地址列表美化 、会员中心缺货登记、会员中心退换货、会员中心退换货详情页 、用户信息、新增会员头像上传功能 、平台红包、物流跟踪功能、资金管理
- 购物车
购物车功能包括:购物车选择购买功能、购物车加强功能、商品促销满减、满赠、折扣功能、凑单功能、简化购物流程、购物车为空时去购物功能、提示购物车商品是否有库存。
- 订单
订单功能包括:商家自定义配送方式和运费、门店自提功能、发票功能、商品无库存时提交订单弹窗提示继续购物、结算页面无货提交弹出框、快递配送方式选择如韵达。
- 积分商城
积分商城功能包括:积分商品列表、兑换商品排序、精品推荐、热门兑换、兑换商品详情页。
- 拍卖活动
拍卖活动功能包括:拍卖活动列表、竞拍商品排序和搜索、拍卖商品详情页、出价记录、商家店铺。
- 优惠活动
优惠活动功能包括:优惠活动列表页、优惠范围、优惠方式。
后台管理系统——项目框架搭建 shop父模块 创建pom项目Maven提供了聚合类型的项目,其本质就是一个分布式架构。
Maven分布式架构-聚合类型,需要一个parent项目,shop-parent就是parent项目。
编辑pom.xml配置文件shop-common子模块4.0.0 com.xxxx shop pom 1.0-SNAPSHOT shop-common shop-generator shop-manager shop-order shop-portal shop-rpc shop-sso org.springframework.boot spring-boot-starter-parent 2.2.2.RELEASE 1.8 2.1.1 1.2.13 8.0.18 1.1.20 org.mybatis.spring.boot mybatis-spring-boot-starter ${mybatis.version} com.github.pagehelper pagehelper-spring-boot-starter ${pagehelper.version} mysql mysql-connector-java ${mysql.version} com.alibaba druid ${druid.version}
用于添加公共的工具类、枚举类、拦截器等。
编辑pom.xml配置文件shop-manager子模块4.0.0 com.xxxx shop-common 1.0-SNAPSHOT com.xxxx shop 1.0-SNAPSHOT UTF-8 1.8 1.8 org.springframework.boot spring-boot-starter-web com.github.pagehelper pagehelper-spring-boot-starter
后台管理系统
编辑pom.xml配置文件4.0.0 com.xxxx shop-manager 1.0-SNAPSHOT com.xxxx shop 1.0-SNAPSHOT UTF-8 1.8 1.8 com.xxxx shop-common 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-freemarker org.mybatis.spring.boot mybatis-spring-boot-starter mysql mysql-connector-java com.alibaba druid org.springframework.boot spring-boot-starter-test test org.junit.vintage junit-vintage-engine
application-dev.yml
server:
port: 9090 # 项目访问端口,默认 8080
servlet: # 项目访问路径,默认 /
context-path: /shop-manager
# Spring
spring:
# 数据源
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: root
# 指定 druid 连接池以及 druid 连接池配置
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 1 # 初始连接数
max-active: 20 # 最大连接数
max-idle: 20 # 最大空闲
min-idle: 1 # 最小空闲
max-wait: 60000 # 最长等待时间
# freemarker 模板引擎
freemarker:
cache: false
charset: UTF-8
content-type: text/html;charset=UTF-8
enabled: true
suffix: .ftl
template-loader-path: classpath:/views/
# 配置模板里是否可以直接取request的属性 request是别名
request-context-attribute: request
# 配置将request和session中的键值添加到
# AbstractTemplateView类的renderMergedOutputModel方法中的model这个Map参数中
expose-request-attributes: true
expose-spring-macro-helpers: true
# 配置模板里是否可以直接取session的属性 true 是允许
expose-session-attributes: true
settings:
tag_syntax: auto_detect # 配置标签语法为自动,页面可以将 <> 改为 [],为了区别 html 标签
template_update_delay: 0 # 模板更新时间,单位秒
default_encoding: UTF-8 # 默认编码字符集
output_encoding: UTF-8 # 模板输出编码字符集
locale: zh_CN # 本地化配置
date_format: yyyy-MM-dd # 日期格式化
time_format: HH:mm:ss # 时间格式化
datetime_format: yyyy-MM-dd HH:mm:ss # 日期时间格式化
number_format: #.## # 数字格式化
boolean_format: true,false # boolean格式化
# ignore,debug,html_debug,rethrow
# 1.TemplateExceptionHandler.IGNORE_HANDLER简单地压制所有异常
# 它对处理异常没有任何作用,也不会重新抛出异常,页面可以正常渲染,后台抛异常
# 2.TemplateExceptionHandler.DEBUG_HANDLER打印堆栈信息和重新抛出异常。这是默认的异常控制器
# 3.TemplateExceptionHandler.HTML_DEBUG_HANDLER和DEBUG_HANDLER相同
# 但是可以格式化堆栈跟踪信息,HTML页面,建议使用它而不是DEBUG_HANDLER
# 4.TemplateExceptionHandler.RETHROW_HANDLER简单重新抛出所有异常而不会做其他的事情
# 5.使用自定义异常类实现TemplateExceptionHandler重写handleTemplateException方法
template_exception_handler: html_debug
# MyBatis
mybatis:
# 配置 MyBatis数据返回类型别名(默认别名是类名)
type-aliases-package: com.xxxx.manager.pojo
# 配置 MyBatis Mapper 映射文件
mapper-locations: classpath:/mapper
@Controller
public class PageController {
@RequestMapping("/{page}")
public String page(@PathVariable String page) {
System.out.println(page);
return page;
}
}
ManagerApplication.java
package com.xxxx.manager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ManagerApplication {
public static void main(String[] args) {
SpringApplication.run(ManagerApplication.class,args);
}
}
Welcome.ftl
<#assign ctx=request.contextPath/>
shop商城后台管理系统
${ctx}欢迎使用shop商城后台管理系统
程序运行前先把parent项目install一下
将html页面修改为freemarker页面 将.html复制到项目中,并将后缀改为.ftl首页.html -> index.ftl
欢迎.html -> welcome.ftl
在.ftl文件顶部设置变量ctx,其值为项目的根路径<#assign ctx=request.contextPath/>批量修改freemarker中资源文件引用路径
css、js、images等资源文件的引用路径
再修改index.ftl的iframe的欢迎页面
Freemarker提取公用模板 公用模板head.ftl
<#assign ctx=request.contextPath/>商品管理-商品分类页面-新增分类页面 提取freemarker页面shop管理后台 <#---->
将商品分类.html和商品分类-新增.html文件导入项目中,修改为category/category-list.ftl和category/category-add.ftl,处理文件内代码,建议先处理静态文件商品分类.html然后再导入项目,因为文件过大,直接导入项目可能会导致IDEA很卡。
在.ftl文件顶部设置变量ctx,其值为项目的根路径<#assign ctx=request.contextPath/>Controller控制层
GoodsController.java
package com.xxxx.manager.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("goods")
public class GoodsController {
@RequestMapping("category/add")
public String categoryAdd(){
return "goods/category/category-add";
}
@RequestMapping("category/list")
public String categoryList(){
return "goods/category/category-list";
}
}
访问测试
修改ftl页面对应的访问地址,然后启动项目进行测试。
index.ftl
category-list.ftl
配置访问根路径对应资源操作
因为每次访问根页面都需要加一个index/,为了方便省事,我们可以通过页面跳转方式来实现:
PageController.java
@RequestMapping("/")
public String index() {
System.out.println("index");
return "index";
}
MyBatis逆向工程generator的使用
generator是什么?
是一个逆向工程,用于自动生成mapper.xml,mapper.java,pojo
generator能干什么? 对于单表而言,几乎是一个全能的工具,能很大提高编程效率。更多的关注业务逻辑的实现。
怎么使用? 创建一个generator项目generator本身和我们商城项目没有关联,所以可以单独新建为一个Project,这边也做成Maven聚合项目里的一个子项目
添加依赖编写mybatis-generator.xml配置文件4.0.0 com.xxxx shop-generator 1.0-SNAPSHOT com.xxxx shop 1.0-SNAPSHOT UTF-8 1.8 1.8 org.mybatis.spring.boot mybatis-spring-boot-starter org.mybatis.generator mybatis-generator-core 1.3.7 org.mybatis.generator mybatis-generator-maven-plugin 1.3.7 true true src/main/resources/mybatis-generator.xml mysql mysql-connector-java ${mysql.version} org.mybatis.generator mybatis-generator-core 1.3.7 com.xxxx shop-generator 1.0-SNAPSHOT
ShopCommentGenerator工具类
package com.xxxx.generator;
import org.mybatis.generator.api.CommentGenerator;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.dom.java.CompilationUnit;
import org.mybatis.generator.api.dom.java.Field;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.InnerClass;
import org.mybatis.generator.api.dom.java.InnerEnum;
import org.mybatis.generator.api.dom.java.JavaElement;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import org.mybatis.generator.api.dom.xml.XmlElement;
import org.mybatis.generator.config.PropertyRegistry;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Properties;
import java.util.Set;
import static org.mybatis.generator.internal.util.StringUtility.isTrue;
public class ShopCommentGenerator implements CommentGenerator {
private Properties properties;
private Properties systemPro;
private boolean suppressDate;
private boolean suppressAllComments;
private String currentDateStr;
public ShopCommentGenerator() {
super();
properties = new Properties();
systemPro = System.getProperties();
suppressDate = false;
suppressAllComments = false;
currentDateStr = DateTimeFormatter.ofPattern("yyyy/MM/dd").format(LocalDateTime.now());
}
protected String getDateString() {
String result = null;
if (!suppressDate) {
result = currentDateStr;
}
return result;
}
@Override
public void addConfigurationProperties(Properties properties) {
this.properties.putAll(properties);
suppressDate = isTrue(properties.getProperty(PropertyRegistry.COMMENT_GENERATOR_SUPPRESS_DATE));
suppressAllComments = isTrue(properties.getProperty(PropertyRegistry.COMMENT_GENERATOR_SUPPRESS_ALL_COMMENTS));
}
@Override
public void addFieldComment(Field field, IntrospectedTable introspectedTable, IntrospectedColumn introspectedColumn) {
if (suppressAllComments)
return;
StringBuilder sb = new StringBuilder();
field.addJavaDocLine("");
}
@Override
public void addFieldComment(Field field, IntrospectedTable introspectedTable) {
if (suppressAllComments)
return;
StringBuilder sb = new StringBuilder();
field.addJavaDocLine("");
}
@Override
public void addModelClassComment(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
if (suppressAllComments) {
return;
}
topLevelClass.addJavaDocLine("");
}
@Override
public void addClassComment(InnerClass innerClass, IntrospectedTable introspectedTable) {
if (suppressAllComments) {
return;
}
// 获取表注释
String remarks = introspectedTable.getRemarks();
innerClass.addJavaDocLine("");
}
@Override
public void addClassComment(InnerClass innerClass, IntrospectedTable introspectedTable, boolean b) {
// 获取表注释
String remarks = introspectedTable.getRemarks();
innerClass.addJavaDocLine("");
}
@Override
public void addEnumComment(InnerEnum innerEnum, IntrospectedTable introspectedTable) {
if (suppressAllComments)
return;
StringBuilder sb = new StringBuilder();
innerEnum.addJavaDocLine("");
}
@Override
public void addGetterComment(Method method, IntrospectedTable introspectedTable, IntrospectedColumn introspectedColumn) {
}
@Override
public void addSetterComment(Method method, IntrospectedTable introspectedTable, IntrospectedColumn introspectedColumn) {
}
@Override
public void addGeneralMethodComment(Method method, IntrospectedTable introspectedTable) {
}
@Override
public void addJavaFileComment(CompilationUnit compilationUnit) {
}
@Override
public void addComment(XmlElement xmlElement) {
}
protected void addJavadocTag(JavaElement javaElement, boolean markAsDoNotDelete) {
}
@Override
public void addRootComment(XmlElement xmlElement) {
}
@Override
public void addGeneralMethodAnnotation(Method method, IntrospectedTable introspectedTable, Set set) {
}
@Override
public void addGeneralMethodAnnotation(Method method, IntrospectedTable introspectedTable, IntrospectedColumn introspectedColumn, Set set) {
}
@Override
public void addFieldAnnotation(Field field, IntrospectedTable introspectedTable, Set set) {
}
@Override
public void addFieldAnnotation(Field field, IntrospectedTable introspectedTable, IntrospectedColumn introspectedColumn, Set set) {
}
@Override
public void addClassAnnotation(InnerClass innerClass, IntrospectedTable introspectedTable, Set set) {
}
}
执行
执行前一定要先install,否则有可能报错
结果 商品分类-新增分类-级联查询 Service服务层GoodsCategoryService.java
package com.xxxx.manager.service;
import com.xxxx.manager.pojo.GoodsCategory;
import java.util.List;
public interface GoodsCategoryService {
List selectCategoryTopList();
List selectCategoryListByParentId(short parentId);
List selectCategoryList(short parentId);
}
GoodsCategoryServiceImpl.java
package com.xxxx.manager.service.impl;
import com.xxxx.manager.mapper.GoodsCategoryMapper;
import com.xxxx.manager.pojo.GoodsCategory;
import com.xxxx.manager.pojo.GoodsCategoryExample;
import com.xxxx.manager.service.GoodsCategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class GoodsCategoryServiceImpl implements GoodsCategoryService {
@Autowired
private GoodsCategoryMapper goodsCategoryMapper;
@Override
public List selectCategoryTopList() {
// 创建查询对象
GoodsCategoryExample example = new GoodsCategoryExample();
// where条件
example.createCriteria().andParentIdEqualTo((short) 0);
return goodsCategoryMapper.selectByExample(example);
}
@Override
public List selectCategoryListByParentId(short parentId) {
// 创建查询对象
GoodsCategoryExample example = new GoodsCategoryExample();
// where条件
example.createCriteria().andParentIdEqualTo(parentId);
return goodsCategoryMapper.selectByExample(example);
}
@Override
public List selectCategoryList(short parentId) {
// 创建查询对象
GoodsCategoryExample example = new GoodsCategoryExample();
// where条件
example.createCriteria().andParentIdEqualTo(parentId);
return goodsCategoryMapper.selectByExample(example);
}
}
Controller控制层
GoodsController.java
package com.ego.manager.controller;
import com.xxxx.manager.pojo.GoodsCategory;
import com.xxxx.manager.service.GoodsCategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
@Controller
@RequestMapping("goods")
public class GoodsController {
@Autowired
private GoodsCategoryService goodsCategoryService;
@RequestMapping("category/add")
public String categoryAdd(Model model){
List gcList = goodsCategoryService.selectCategoryTopList();
model.addAttribute("gcList",gcList);
return "goods/category/category-add";
}
@RequestMapping("category/list")
public String categoryList(){
return "goods/category/category-list";
}
@RequestMapping("category/{parentId}")
@ResponseBody
public List selectCategoryList(@PathVariable Short parentId){
return goodsCategoryService.selectCategoryList(parentId);
}
}
ftl页面
category-add.ftl
function getCategory(id, next, select_id) {
var url = '${ctx}/goods/category/' + id;
// 用户重新选择顶级分类时,重置下级分类为:请选择商品分类,且清空下级分类信息
var htmlStr = "";
if(0==id){
$("#"+next).html(htmlStr);
return;
}
$.ajax({
type: "GET",
url: url,
error: function (request) {
layer.alert("获取子分类失败!");
},
success: function (result) {
if (result.length > 0) {
for (i = 0; i < result.length; i++) {
htmlStr += ""
}
$("#"+next).html(htmlStr);
}else {
layer.alert("获取子分类失败!");
}
}
});
}
商品分类-新增分类-保存
编写公共保存状态返回对象
shop-common的BaseResultEnum.java
package com.xxxx.common.enums;
public enum BaseResultEnum {
// 自定义枚举类
SUCCESS(200, "成功"),
ERROR(400, "失败"),
PASS_ERROR_01(501, "两次输入的密码不一致"),
PASS_ERROR_02(502, "输入的原始密码不正确"),
PASS_ERROR_03(503, "输入的验证码不正确"),
PASS_ERROR_04(504, "用户名或者密码错误");
private Integer code;
private String message;
private BaseResultEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
shop-common的BaseResult.java
package com.xxxx.common.result;
import com.github.pagehelper.PageInfo;
import com.xxxx.common.enums.BaseResultEnum;
import java.io.Serializable;
public class BaseResult implements Serializable {
// 状态编码
private Integer code;
// 状态描述
private String message;
// 分页对象(商品列表需要-pom.xml添加依赖)
PageInfo> pageInfo;
//成功返回的对象
public static BaseResult success() {
BaseResult result = new BaseResult();
result.setCode(BaseResultEnum.SUCCESS.getCode());
result.setMessage(BaseResultEnum.SUCCESS.getMessage());
return result;
}
//成功返回的对象-带分页对象
public static BaseResult success(PageInfo> pageInfo) {
BaseResult result = new BaseResult();
result.setCode(BaseResultEnum.SUCCESS.getCode());
result.setMessage(BaseResultEnum.SUCCESS.getMessage());
result.setPageInfo(pageInfo);
return result;
}
//失败返回的对象
public static BaseResult error() {
BaseResult result = new BaseResult();
result.setCode(BaseResultEnum.ERROR.getCode());
result.setMessage(BaseResultEnum.ERROR.getMessage());
return result;
}
public BaseResult(Integer code, String message) {
this.code = code;
this.message = message;
}
public BaseResult() {
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public PageInfo> getPageInfo() {
return pageInfo;
}
public void setPageInfo(PageInfo> pageInfo) {
this.pageInfo = pageInfo;
}
@Override
public String toString() {
return "BaseResult [code=" + code + ", message=" + message + "]";
}
}
Service服务层
GoodsCategoryService.java
int categorySave(GoodsCategory goodsCategory);
GoodsCategoryServiceImpl.java
@Override
public int categorySave(GoodsCategory goodsCategory) {
return goodsCategoryMapper.insertSelective(goodsCategory);
}
Controller控制层
GoodsController.java
@RequestMapping("category/save")
@ResponseBody
public BaseResult CategorySave(GoodsCategory goodsCategory) {
int i = goodsCategoryService.categorySave(goodsCategory);
//三目运算符
return i > 0 ? BaseResult.success() : BaseResult.error();
}
ftl页面
category-add.ftl
添加l两个隐藏域,根据用户操作onchange动态赋值传往后台
修改页面form表单中name的域名称与GoodsCategory对象中的属性一一对应
js动态赋值,实现保存新增分类
function getCategory(id, next, select_id) {
var url = '${ctx}/goods/category/' + id;
// 用户重新选择顶级分类时,重置下级分类为:请选择商品分类,且清空下级分类信息
var htmlStr = "";
//修改parentId
$("#parentId").val(id);
if (0 == id) {
$("#" + next).html(htmlStr);
//修改level
$("#level").val(1);
return;
}
$.ajax({
type: "GET",
url: url,
error: function (request) {
layer.alert("获取子分类失败!");
},
success: function (result) {
if (result.length > 0) {
for (i = 0; i < result.length; i++) {
htmlStr += ""
}
$("#" + next).html(htmlStr);
$("#level").val(2);
} else {
layer.alert("获取子分类失败!");
}
}
});
}
function ajaxSubmitForm() {
$.ajax({
url: "${ctx}/goods/category/save",
type: "post",
data: $("#category_form").serialize(),
dataType:"JSON",
success:function (result) {
if (result.code==200){
layer.confirm("保存成功",{btn:['继续新增','返回列表']},
function () {
window.location.href="${ctx}/goods/category/add";
},function () {
window.location.href="https://blog.csdn.net/m0_51071144/article/details/${ctx}/goods/category/list";
});
}else {
layer.alert("保存失败");
}
},
error:function () {
layer.alert("保存失败");
}
});
}
function setParentId(parentId,level) {
// 修改parentId和level
if (0==parentId){
$("#parentId").val($("#parent_id_1").val);
$("#level").val(2);
return;
}
// 修改parentId和level
$("#parentId").val(parentId);
$("#level").val(level);
}
商品分类-列表页
分析页面结构
页面三级之间并无直接关系,而是由html、css页面样式实现了分级样式。
页面结构三个普通tr,不同等级的tr缩进值(左边距)不同。
第一级第二级 第三级
后台只需要获取每一级的商品列表内容返回页面循环处理展示即可
后台代码实现 shop-manager-pojo添加VO对象GoodsCategoryVo.java
package com.xxxx.manager.vo;
import java.io.Serializable;
import java.util.List;
public class GoodsCategoryVo implements Serializable {
private List children;
private Short id;
private String name;
private String mobileName;
private Short parentId;
private String parentIdPath;
private Byte level;
private Byte sortOrder;
private Byte isShow;
private String image;
private Byte isHot;
private Byte catGroup;
private Byte commissionRate;
private static final long serialVersionUID = 1L;
public List getChildren() {
return children;
}
public void setChildren(List children) {
this.children = children;
}
public Short getId() {
return id;
}
public void setId(Short id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name == null ? null : name.trim();
}
public String getMobileName() {
return mobileName;
}
public void setMobileName(String mobileName) {
this.mobileName = mobileName == null ? null : mobileName.trim();
}
public Short getParentId() {
return parentId;
}
public void setParentId(Short parentId) {
this.parentId = parentId;
}
public String getParentIdPath() {
return parentIdPath;
}
public void setParentIdPath(String parentIdPath) {
this.parentIdPath = parentIdPath == null ? null : parentIdPath.trim();
}
public Byte getLevel() {
return level;
}
public void setLevel(Byte level) {
this.level = level;
}
public Byte getSortOrder() {
return sortOrder;
}
public void setSortOrder(Byte sortOrder) {
this.sortOrder = sortOrder;
}
public Byte getIsShow() {
return isShow;
}
public void setIsShow(Byte isShow) {
this.isShow = isShow;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image == null ? null : image.trim();
}
public Byte getIsHot() {
return isHot;
}
public void setIsHot(Byte isHot) {
this.isHot = isHot;
}
public Byte getCatGroup() {
return catGroup;
}
public void setCatGroup(Byte catGroup) {
this.catGroup = catGroup;
}
public Byte getCommissionRate() {
return commissionRate;
}
public void setCommissionRate(Byte commissionRate) {
this.commissionRate = commissionRate;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
sb.append(", id=").append(id);
sb.append(", name=").append(name);
sb.append(", mobileName=").append(mobileName);
sb.append(", parentId=").append(parentId);
sb.append(", parentIdPath=").append(parentIdPath);
sb.append(", level=").append(level);
sb.append(", sortOrder=").append(sortOrder);
sb.append(", isShow=").append(isShow);
sb.append(", image=").append(image);
sb.append(", isHot=").append(isHot);
sb.append(", catGroup=").append(catGroup);
sb.append(", commissionRate=").append(commissionRate);
sb.append("]");
return sb.toString();
}
}
Service服务层
GoodsCategoryService.java
ListselectCategoryListForView();
GoodsCategoryServiceImpl.java
@Override public ListService服务层的测试类selectCategoryListForView() { //创建查询对象 GoodsCategoryExample example = new GoodsCategoryExample(); //where条件 parentId=0 and level = 1 example.createCriteria().andParentIdEqualTo((short) 0).andLevelEqualTo((byte) 1); //查询一级分类 List gcList01 = goodsCategoryMapper.selectByExample(example); //处理一级分类查询下级分类 List gcvList01 = new ArrayList<>(); for (GoodsCategory gc01 : gcList01) { //将GoodsCategory对象转成GoodsCategoryVo对象 GoodsCategoryVo gcv01 = new GoodsCategoryVo(); BeanUtils.copyProperties(gc01,gcv01); // 清空查询对象 example.clear(); //设置查询参数 example.createCriteria().andParentIdEqualTo(gcv01.getId()).andLevelEqualTo((byte) 2); //查询二级分类 List gcList02 = goodsCategoryMapper.selectByExample(example); //处理二级分类查询下级分类 List gcvList02 = new ArrayList<>(); for (GoodsCategoryVo gc02 : gcvList02) { //将GoodsCategory对象转成GoodsCategoryVo对象 GoodsCategoryVo gcv02 = new GoodsCategoryVo(); BeanUtils.copyProperties(gc02,gcv02); // 清空查询对象 example.clear(); //设置查询参数 example.createCriteria().andParentIdEqualTo(gcv02.getId()).andLevelEqualTo((byte) 3); //查询三级分类 List gcList03 = goodsCategoryMapper.selectByExample(example); // 处理三级分类添加至二级二级分类vo对象 List gcvList03 = new ArrayList<>(); for (GoodsCategory gc03 : gcList03) { //将GoodsCategory对象转成GoodsCategoryVo对象 GoodsCategoryVo gcv03 = new GoodsCategoryVo(); BeanUtils.copyProperties(gc03,gcv03); //将对象添加至三级分类vo对象List gcvList03.add(gcv03); } // 将三级分类List添加至二级分类 gcv02.setChildren(gcvList03); // 讲对象添加至二级分类Vo对象List gcvList02.add(gcv02); } // 将二级分类List添加至一级分类 gcv01.setChildren(gcvList02); // 将对象添加至一级分类Vo对象List gcvList01.add(gcv01); } //=====================JDK8新特性========================= //创建查询对象 // GoodsCategoryExample example = new GoodsCategoryExample(); // //查询所有的商品分类list // List list = goodsCategoryMapper.selectByExample(example); // //将List 转成List // List gcvList = list.stream().map(e -> { // GoodsCategoryVo gcv = new GoodsCategoryVo(); // BeanUtils.copyProperties(e, gcv); // return gcv; // }).collect(Collectors.toList()); // // Map > map = // gcvList.stream().collect(Collectors.groupingBy(GoodsCategoryVo::getParentId)); // // gcvList.forEach(e -> e.setChildrenList(map.get(e.getId()))); // // List gcvList01 = gcvList.stream().filter(e -> 1 == e.getLevel()).collect(Collectors.toList // ()); //=====================JDK8新特性========================= return gcvList01; }
GoodsCategoryServiceITest.java
package com.xxxx.manager;
import com.xxxx.manager.service.GoodsCategoryService;
import com.xxxx.manager.vo.GoodsCategoryVo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest(classes = ManagerApplication.class)
public class GoodsCategoryServiceTest {
@Autowired
private GoodsCategoryService goodsCategoryService;
@Test
public void testSelectCategoryListForView(){
List gcvList = goodsCategoryService.selectCategoryListForView();
System.out.println(gcvList);
}
}
Controller控制层
GoodsController.java
@RequestMapping("category/list")
public String categoryList(Model model) {
List gcvList = goodsCategoryService.selectCategoryListForView();
model.addAttribute("gcvList",gcvList);
return "goods/category/category-list";
}
ftl页面
category-list.ftl 的中的所有内容
分页



