通用型产品(电商)发布解决方案+落地实现(基于分布式微服务技术栈:
SpringBoot+Spring Cloud+Spring Cloud Alibaba Vue+ElementUl +MyBatis-Plus
+ MySQL Git Maven Linux Nginx Docker +前后端分离)
项目技术栈和前置技术
项目技术栈
1.前端:Html、Css、Js、Vue、ElementUI、Axios、JQuery、Node.js等
2.框架:Spring Boot、Spring Cloud、MyBatis-Plus
3.数据库:MySQL
4.项目管理:Git、Maven
5.平台:Linux、Docker
6.Alibaba/Sentinel:流量控制、熔断降级、系统负载保护
7.Alibaba/Nacos:注册中心
8.GateWay:网关服务
9.Alibaba/云存储OOS
10.快速开发技术:代码生成器、Vue前端脚手架项目Vue-cli
11.工具:Virtual box(类似vmware)、Vagrant
12.常用技术:Postman、浏览器跨域技术
13.Nginx:反向代理、负载均衡、动静分离
14.SPU,SKU表设计方案
15.更多…
环境配置
登录Vagrant
需要在虚拟系统的文件夹,打开cmd
登录: vagrant ssh
如果报错
vagrant@127.0.0.1: Permission denied (publickey,gssapi-keyex,gssapi-with-mic)
解决办法如下:
set VAGRANT_PREFER_SYSTEM_BIN=0
这个命令的作用是告诉Vagrant使用自己的ssh客户端,而不是Windows ssh客户端。
或将其放入您的.bash_profile:(没有找到该文件,不知道该方法是否可行)
export VAGRANT_PREFER_SYSTEM_BIN=0
查看ip地址:
windows: ipconfig
linux: ifconfig
virtualBox: ip a
查看eth1的inet
查看当前镜像:sudo docker images
Docker的使用
启动Docker: sudo systemctl start docker
client端的指令:
docker pull 拉取镜像
docker run 创建并启动容器
sudo docker exec -it mysql /bin/bash 创建并运行,只需执行一次
//语法说明: mysql 这儿是自定义的容器名
mysql -u root -p 执行mysql
sudo docker stop/start/restart mysql(这儿是容器的名称,自定义的name)
//以后可以使用它来关闭、打开或重启
docker ps 查看启动的容器执行情况
docker images
容器自动启动
通过docker update-restarta=always容器名称命令可以设置Docker中的mysql、redis等自动启动
切换root用户
su root 切换到 root 用户
root 默认密码就是 vagrant
exit 退出;即可返回登陆时的用户
Mysql
正确安装后启动mysql(非第一次启动)
sudo docker restart mysql
需要使用重新启动命令,原因是超过8小时,mysql自动断开
sudo docker start mysql
本来就是启动的,使用这条命令不会刷新时间
解决超时8小时,mysql断开连接的问题(未解决)
主要问题是找不到文件,以及无法使用vim
linux的vagrant上运行mysql程序:sudo docker exec -it mysql /bin/bash
登录mysql: mysql -u root -p
#然后输入命令查看mysql的保存位置
root@9939dd1fabf2:/# whereis mysql
我的位置显示如下:
mysql: /usr/bin/mysql /usr/lib/mysql /etc/mysql /usr/share/mysql
#切换目录
cd /etc/mysql/mysql.conf.d
#修改文件
vim mysqld.cnf
#在 [mysqlid] 最后一行添加配置
sql_mode = NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
或者
wait_timeout=1814400
Gitee创建新项目
搭建产品发布系统 后端 实现
快速开发平台
欢迎使用renren-fast人人快速开发平台-Powered By https://www.renren.io/
Java项目快速开发脚手架
https://zhuanlan.zhihu.com/p/145987215
pom.xml的设置
<module>module名</module>
经测试发现,module名是按照生成的文件夹名称来确定的,如果文件夹有两层的话,两层的名称都要写上,比如renren-fast/renren-fast。当时把文件夹重复了。
而不跟<artifactID>,<name>这些关联。
将自己创建的pom.xml的加入到maven
自己创建的pom.xml没有生效,图标是这样的:

点开maven,找到“+”号添加,选择父工程的pom.xml,确定,刷新。生效后,变成:

接着发现,子项目原本显示异常的Java图标,也变成了C这样的正常显示:

创建后台基础数据库和表
创建数据库hspliving_manage,“基字符集”选择utf8mb4的编码,mb4就是most bytes4的意思,用来兼容四字节的unicode,可以支持常见的moji表情图标
运行renren-fast踩的坑
解决办法:MySQL连接报错No appropriate protocol
大概原因:jdk1.8的高版本对SSL协议做了限制
我的解决办法:在sql的url中添加了 useSSL=false
安装NodeJs
需要安装v10.16.3,我的版本太高了。导致后面报错:
<% if (process.env.NODE_ENV === 'production') { %> <% }else { %> <% } %>
卸载Node.js,然后重装该版本。删除了renren-fast-vue,全部重来,终于打开了页面。
NodeJs设置淘宝镜像
在安装node.js时,会安装npm,npm是随nodejs安装的一款包管理工具,类似Maven,给npm设置淘宝镜像。运行cmd:
npm config set registry http://registry.npm.taobao.org
配置npm
在cmd的对应目录下执行如下指令:
npm install chromedriver --chromedriver_cdnurl=http://cdn.npm.taobao.org/dist/chromedriver
D:\idea_java_projects\renren-fast-vue>npm install
D:\idea_java_projects\renren-fast-vue>npm run dev
上面这两条命令都需要在cmd运行。因为IDEA的命令行会报错“因为在此系统上禁止运行脚本”
可以在cmd执行npm,查看是否已经正常安装
提示:直接到目录下,通过地址栏进入cmd,可以直接进入到该目录,免于录入目录名称
注意事项和细节说明
1.注意要保证启动renren-fast后台模块,否则看不到验证码
2.保证renren-fast后台:io/renren,/config/CorsConfig-java,跨域设置是开启的
前端项目配置npm
前面是通过npm run dev语句来启动npm的,现在直接把它在IDEA里面运行
在前端项目的IDEA,点开运行的下拉列表,找到Edit Configuration...,弹出对话框后,点击左上角的“+”号,Add New Configuration,找到npm。右侧脚本Script选择dev,其他正常情况是默认填好的,然后确定。点击运行。
创建表和数据
创建商品分类表(支持层级)
建立一个父级字段,类似指针的方式,指向父级,实现了子级和父级在一张表内存储
renren-generator项目介绍
一句话:renren-generator是人人开源项目的代码生成器,可在线生成entity、xml、dao、service、html、js、sql代码,减少70%以上的开发任务
终于有种解放了的感觉,原来码农还是脱离了“原始社会”的。
搭建renren-generator模块/项目步骤
地址:https://gitee.com/renrenio/renren-generator
配置生成后,放到后端的文件夹。会发现大量报错,接下来就是去完善缺少的一些类和依赖,将他们组成一个common公共Module模块(比如工具类,安全配置)。从renren-fast的common中抽取出自己需要的。
然后在steinliving-commodify中引入该模块,便可引用里面的类
测试
使用postman进行测试,地址按照controller的路径来。
前端
前端页面快速自动生成
---有这么好了吗?
使用renren-generator生成的前端页面,集成到项目中
将生成的renren\main\resources\src\views\modules\commodity目录下的category.vue和category-add-or-update.vue拷贝到renren-fast-vue\src\views\modules\commodity\目录下,没有目录就创建。
按层级返回json数据
在service层处理数据分类,会使用到java8的流式计算(stream API)+递归操作(有一定难度)
在entity添加用于递归的字段:
@TableField(exist = false)
private List<CategoryEntity>childrenCategories;
把多个对象转为List
List<Person> list = Arrays.asList(person1,person2,person3,person4,person5);
把list转为流对象stream,这样就可以调用流的方法,处理一些复杂的业务

使用filter进行过滤,保留断言为真的数据
使用collect进行收集,collect()传入Collector,将数据收集到集合
【疑惑:Collectors.toList( ) 和Arrays.asList( )如何使用?】
map操作:希望给过滤得到的person对象加入cat
注意事项:
filter 不会影响原始引用中的数据。只返回true/false
map 操作会影响原始引用中的数据。因为它return这个结果了
stream常用的一些方法:filter(对数据进行过滤)、map(对数据进行映射操作)、Iimit(限制数据条数)、count (求取数据量)、sorted(排序)、collect(收集集合)等
后端层级树形结构
最先是在service层来处理业务。
获取数据库的所有信息。如何获取呢?
ctrl+B查看父类ServiceImpl,可以看到继承的属性baseMapper,可以通过BaseMapper看到它的方法有哪些。现在使用的是baseMapper.selectList(null)来获取所有数据。
显示所获得的entities数据,便于调试
现在才是controller中设置一个uri连接,实现网页的链接
添加entity属性字段,并注解@TableField(exist = false),避免以前数据封装出错
然后在Service层完善数据层级树形结构
组装成层级树形结构[使用到java8的stream api+递归操作
步骤思路
2.1过滤filter,返回1级分类
2.2进行map映射操作,给每个分类设置对应的子分类(这个过程会使用到递归)
2.3进行排序sorted操作
2.4将处理好的数据收集collect/转换到集合
前端树形层级显示
使用elementUI。为什么不用elementPlus,如何技术选型
引用到新创建的category.vue,但是如何修改代码呢?
获取层级信息data,是通过生命周期的created()主动调用this.$http()的所在方法获取的。
踩坑:created()调用方法时,需要使用this.方法名()的形式来调用,否则找不到
{data}中的大括号,表示里面的data是解构,返回值中只保留data属性的值。
删除Delete
后端部分
逻辑删除
除了可以通过配置application.yml
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
还可以通过设置注解的参数来配置
@TableLogic(value ="1",delval ="0")
value表示逻辑已删除,delval表示逻辑未删除。
如果已经在yaml配置了,只使用注解@TableLogic标注对应字段就可以了,不再写参数。
测试
使用postman,在Json格式中输入[651,652] 之类的数据来进行删除,发现底层运行的是UPDATE
删除实现
在后端controller里面,已经实现。调用即可。
前端部分:完善remove( )方法
以下需求,都可以参考src/views/modules/sys/role.vue里面的代码
需求1:实现删除超链接的正常功能。
this.$http({
//注释掉的这种写法会出错
// url: this.$http.adornUrl('http://localhost:9090/commodity/category/delete'),
url: 'http://localhost:9090/commodity/category/delete',
method: 'post',
// 发出请求时,携带的参数值
data: this.$http.adornData(ids, false)
}).then(({data})=>{
this.getCategories()
})
需求2:点删除后,需要出现提示信息确认删除。
this.$confirm(`确定对【${data.name}】进行 '删除' 操作?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(({data}) => {
this.$http({
//需求1的代码
}).then(({data})=>{
//需求3的代码
this.getCategories()
}).catch(()=>{
//取消不进行任何操作
})
})
需求3:删除成功后,显示提示信息
this.$message({
message: '操作成功',
type: 'success',
})
需求4:上述方法会自动折叠显示的数。需要将删除的结点,保持展开。
参考elementUI的Tree树形控件:默认展开和默认选中
1.模板中引入“ :default-expanded-keys="expandedKey" ”
2.常量池引入变量 expandedKey:[]
3.方法中,对该变量进行赋值
把数组中的值换成需要展开的父级id即可
放在this.getCategories()的前面或者后面都可以
这儿选用node是因为,使用data的话没有数据。估计node是全局变量,data不是。
//数组格式,没有[]中括号,会不显示
this.expandedKey= [node.data.parentId]
新增Append
使用elementUI的Dialog对话框。添加的内容会使dev报错。
用<div></div>把template的内容包起来,就可以避免报错,再进一步修整。
遇到的问题:
超链接是各个append,是如何将对话框对应上的?
append是之前创建的超链接,在其方法里面调用visible=true,即可显示对话框
是哪一步的完善,让前端的分类正确显示了的
在设置了<div>对的前提下。将v-model="category.name"的变量名设置好,就正确显示前端页面了。
category是如何在不同的方法中被调用的?
通过在数据池设置category的所有属性的默认值,这样在方法中进行修改值,然后提交的操作。
data数据不全
在then方法里面data没有数据。是通过使用node这样的全局变量,或者category的值来处理的。
addCategory(data) {//调用后端添加分类
console.log("data",data)
this.$http({
url: 'http://localhost:9090/commodity/category/save',
method: 'post',
// 发出请求时,携带的参数值
data: this.$http.adornData(this.category, false)
}).then(({}) => {
this.$message({
message: '操作成功',
type: 'success',
})
// console.log("node=",node)
//刷新分类列表
this.getCategories()
//数组格式,没有[]中括号,会不显示
this.expandedKey = [this.category.parentId]
this.dialogVisible = false
})
},
append(data) {//打开对话框
// console.log(data)
this.dialogVisible = true
this.category.parentId = data.id
// 乘以*1是为了避免当做String,做成了字符拼接的操作
this.category.catLevel = data.catLevel*1 + 1
console.log("this.category=>",this.category)
},
修改Update
回显信息
打开update的对话框,设置category的属性值
url动态获取,改为用反引号``括起来。
注意这儿使用的${data.id}语句
因为是路径变量,不需要传参,所以adornData注销了
update(data){//更新category的前端表单数据
this.categoryType = "update"
this.dialogVisible=true
// console.log("data=>", data)
this.category.id=data.id
this.$http({
url: `http://localhost:9090/commodity/category/info/${data.id}`,
method: 'get',
// 发出请求时,携带的参数值。此处不需要携带id
// data: this.$http.adornData(this.data, false)
}).then(({data})=>{
//注意数据的结构,先输出查看后,再进行赋值
// console.log("category=>",data)
this.category.name=data.category.name
this.category.icon=data.category.icon
this.category.proUnit=data.category.proUnit
this.category.parentId=data.category.parentId
this.category.catLevel=data.category.catLevel
})
},
修改信息
真正保存category的信息到数据库进行保存
updateFromDB(){//真正到数据库更新数据
this.$http({
url: 'http://localhost:9090/commodity/category/update',
method: 'post',
// 发出请求时,携带的参数值
data: this.$http.adornData(this.category, false)
}).then(({}) => {
this.$message({
message: '操作成功',
type: 'success',
})
// console.log("node=",node)
//刷新分类列表
this.getCategories()
//数组格式,没有[]中括号,会不显示
this.expandedKey = [this.category.parentId]
this.dialogVisible = false
})
},
3. 设置变量categoryType
通过设置变量categoryType,在打开对话框时,确定是append操作还是update操作,来明确“确定”键的具体功能调用。
addOrUpdate(){
if(this.categoryType==="update"){
this.updateFromDB()
}
if(this.categoryType==="add"){
this.addCategory()
}
},
批量删除
核心步骤:获取当前选中的结点
在el-tree控件中,添加属性ref="categoryTree",标记需要从哪个el-tree控件获取信息
然后有两个方法可以获取结点:
this.$refs.tree.getCheckedNodes() 获取结点的所有信息,推荐使用
this.$refs.tree.getCheckedKeys() 只获取id值
通过下标遍历访问获取ids和names
使用names回显名称,确定后,使用ids调用后端进行逻辑删除
batchDelete(){
var ids=[]
var categoryNames=[]
//获得当前选中的条目
var checkedNodes = this.$refs.categoryTree.getCheckedNodes();
console.log("getCheckedNodes=>",checkedNodes)
for (let i = 0; i < checkedNodes.length; i++) {
ids.push(checkedNodes[i].id)
categoryNames.push(checkedNodes[i].name)
}
// console.log("names=",categoryNames)
this.$confirm(`确定对【${categoryNames}】进行 '批量删除' 操作?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(({data}) => {
this.$http({
//注释掉的这种写法会出错
// url: this.$http.adornUrl('http://localhost:9090/commodity/category/delete'),
url: 'http://localhost:9090/commodity/category/delete',
method: 'post',
// 发出请求时,携带的参数值
data: this.$http.adornData(ids, false)
}).then(({data}) => {
this.$message({
message: '批量删除操作成功',
type: 'success',
})
// console.log("node=",node)
//刷新分类列表
this.getCategories()
}).catch(() => {
//取消不进行任何操作
})
})
},
完成家居品牌的—增删改查
在DB,创建家居品牌表
使用renren-generator生成代码
启动RenrenApplicationGener服务
访问localhost:80
导航菜单点击renren-fast
右侧会显示DB里面存在的表。是因为在:renren-fast/src/main/resources/application-dev.yml配置了对应的DB信息。
选中新建的品牌表,点击“生成代码”。
将解压后的main文件夹,去覆盖steinliving-commodity的main文件夹即可(提醒:注意不要把steinliving-commodity项目原有的文件/controller/service/dao等覆盖了,正常不会出现覆盖提示)
调整修复报错
BrandConctroller:注销与@RequiresPermissions有关的所有注解和引用
再重启该服务,查看是否正常启动
使用postman完成测试
注意:update的测试参数,是用的json格式完成的。
{
"id":1,
"name":"海信"
}
新建家居品牌菜单
在人人开发平台,系统管理->菜单管理 里面新增“菜单”
将生成的renren\main\resources\src\views\modules\commodity目录下的brand.vue和brand-add-or-update.vue拷贝到renren-fast-vue\src\views\modules\commodity\目录下
修改renren-fast-vue\src\views\modules\commodity\brand.vue和brand-add-or-update.vue把所有url的值(因为还会使用到添加/删除/修改等方法),都改成指向http://ocalhost:9090服务器(说明:后面我们使用Gateway微服务可以完美解决前端项目指向多个微服务的问题/放心)
修改Vue文件,使得前端页面能够正常显示
前端文件位置:src/views/modules/commodity/brand.vue
修改this.$http里面的url属性值,涉及查询和删除两条。事实上,把查询的list的url修改正确后,前面页面便可以显示了
打开权限检测,会出现添加和批量删除按钮
修改isAuth方法,暂时放行,renren-fast-vue\src\utils\index.js
注释原来的return,改为 return true 即可。
查询功能
不起作用。可以手动修改来实现
src/views/modules/commodity/brand-add-or-update.vue
将其查询和更新的url修改一下。
新增功能
注意新增"isshow"字段是int类型,需要输入数字才能正常录入
swift切换控件--还是在elementUI里面
需要用到table的自定义组件,使用插槽机制
最后完成后,发现刷新swift组件状态显示不正常。原因是需要在
:active-value="1"
:inactive-value="0"
这两个属性前加上":"冒号,表示它绑定的是Number值(上面的已添加)。然后刷新即可正常显示
但是看说明呢,“:”冒号表示的意思,应该是v-bind绑定的别名,即简写。
注意事项
root.id==category.id 出现的bug。
因为他们都是Long包装类,当数值<-128或者>127的时候,==判断的引用类型并不是同一个,会出错,不能正常显示分类。使用.equals()方法来解决这个问题。