从0开始搭建一个项目
用到的技术:springboot+vue+mybatis+elementui+mysql
1.创建springboot项目
1.引入pom依赖
<dependencies>
<!-- 模板引擎-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- web项目依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 简化实体类,自动创建get set等方法-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试相关-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- mybatis依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!-- mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- json数据处理-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.15</version>
</dependency>
<!-- 分页助手 mybatis分页查询需要的依赖-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.13</version>
</dependency>
</dependencies>
2.配置文件
#项目端口
server.port=8899
#mysql数据库
spring.datasource.hikari.max-lifetime=120000
spring.datasource.url= jdbc:mysql://47.98.252.xxx:xxxx/atguigudb
spring.datasource.username=数据库用户名
spring.datasource.password=数据库密码
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.hikari.max_lifetime=120000
#mysql字段驼峰命名 数据库中下划线的字段会被自动分隔变成大写字母
mybatis.configuration.map-underscore-to-camel-case=true
#指定xml文件路径
mybatis.mapper-locations=classpath:mapper
3.创建欢迎页
此工程欢迎页就是登录页 templates文件夹下index.html文件 页面代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
<script src="/vue/vue.js" rel="stylesheet" type="text/javascript"></script>
<script src="/element-ui/lib/index.js"></script>
<link rel="stylesheet" href="/element-ui/lib/theme-chalk/index.css">
<script src="/axios/axios.min.js"></script>
<script src="/vue-router/vue-router.min.js"></script>
</head>
<body style="background: linen">
<div id="test" >
<h1 align="center">请输入账号和密码</h1>
<br>
<br>
<br>
<br>
<h5 align="center" style="color: red">{{errorMsg}}</h5>
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="800px"
class="demo-ruleForm" >
<h5 align="center" style="color: #C0C4CC">账号:admin 密码:任意</h5>
<el-form-item label="账号" prop="username">
<el-input style="width:285px;" v-model="ruleForm.username" prefix-icon="el-icon-user"
placeholder="请输入账号" clearable></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input style="width:285px;" v-model="ruleForm.password"
prefix-icon="el-icon-unlock" placeholder="请输入密码" show-password clearable></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">立即登录</el-button>
</el-form-item>
</el-form>
</div>
<script>
let rt = new VueRouter({
mode: 'history',
routes: [{
path: "/item",
name: "ok",
}, {
path: "/reg",
}]
})
new Vue ({
el:"#test",
router: rt,
data: {
errorMsg:'',
ruleForm: {
username: '',
password:'',
},
rules: {
name: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
],
}
},
methods: {
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
axios.post("/doLogin", this.ruleForm).then(res => {
console.log(res);
if(res.data===123 && res.status===200)
this.$router.push("item");
else this.errorMsg="账号:admin~"
})
} else {
console.log('error submit!!');
return false;
}
});
}
},
watch: {
'$route' (to, from) {
this.$router.go(0);
}
},
})
</script>
</body>
</html>
页面效果:
4.处理登录请求
正常逻辑是去数据库根据用户名获取用户信息,然后进行密码匹配,匹配正确正常返回,异常返回异常信息。 这里处理逻辑很简单,判断传过来的username是不是admin,是的话返回123,不是的话返回0。
@RequestMapping("/doLogin")
@ResponseBody
public String doLogin(@RequestBody User user){
System.out.println(user);
if(user.getUsername().equals("admin"))
return "123";
else return "0";
}
5.登录成功进行页面跳转
设置路径为item,然后进行页面刷新,跳转到localhost:8899/item,展示employee信息
6.接收item请求
跳转到item.html页面
@RequestMapping("/item")
public String afterLogin(){
return "item";
}
7.item.html
展示员工信息
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>employees</title>
<script src="/vue/vue.js" rel="stylesheet" type="text/javascript"></script>
<script src="/element-ui/lib/index.js"></script>
<link rel="stylesheet" href="/element-ui/lib/theme-chalk/index.css">
<script src="/axios/axios.min.js"></script>
<script src="/vue-router/vue-router.min.js"></script>
</head>
<body>
<div id="item" class="item" >
<h3 align="right" >你好:{{username}}</h3>
<h3 style="color: #a0cfff;" align="left" >
员工信息展示
</h3>
<div>
入职日期:
<div class="block" style="display: inline-block">
<el-date-picker
style="width: 195px"
value-format="yyyy-MM-dd"
v-model="search.searchDate"
type="date"
placeholder="请选择入职日期起点">
</el-date-picker>
<el-date-picker
style="width: 195px"
value-format="yyyy-MM-dd"
v-model="search.searchDateEnd"
type="date"
placeholder="请选择入职日期终点">
</el-date-picker>
</div>
姓名:
<el-input
style="width: 185px"
placeholder="请输入部分姓名"
prefix-icon="el-icon-search"
v-model="search.searchName">
</el-input>
<el-button type="primary" round @click="getEmployees()" icon="el-icon-search">
条件搜索
</el-button>
<el-button type="primary" round @click="getDepartments()" icon="el-icon-menu">
部门总览
</el-button>
</div>
<template>
<el-table
:data="employeesData"
style="width: 100%"
:row-class-name="tableRowClassName">
<el-table-column
prop="employeeId"
label="编号"
width="180">
</el-table-column>
<el-table-column
prop="firstName"
label="姓氏"
width="180">
</el-table-column>
<el-table-column
prop="lastName"
label="名字"
width="180">
</el-table-column>
<el-table-column
prop="email"
label="邮箱"
width="180">
</el-table-column>
<el-table-column
prop="phoneNumber"
label="电话"
width="180">
</el-table-column>
<el-table-column
prop="hireDate"
label="入职日期"
width="180">
</el-table-column>
<el-table-column
prop="salary"
label="工资"
width="180">
</el-table-column>
<el-table-column
prop="departmentName"
label="部门名称"
>
</el-table-column>
<el-table-column>
<template slot-scope="scope">
<el-button type="text" @click="getDepartmentInfo(scope.row.departmentId)">
部门信息
</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog title="部门信息" :visible.sync="dialogTableVisible">
<el-table :data="departmentData">
<el-table-column prop="departmentId" label="部门编号" width="150"></el-table-column>
<el-table-column prop="departmentName" label="部门名称" width="200"></el-table-column>
</el-table>
</el-dialog>
<div class="block">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="page.pageNum"
:page-sizes="[5, 10, 15, 20,25,30,35,40,45,50]"
:page-size="page.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="page.total">
</el-pagination>
</div>
</template>
</div>
<script>
let rt = new VueRouter({
mode: 'history',
routes: [{
path: "/departments",
name: "ok",
}, {
path: "/reg",
}]
})
new Vue({
el:"#item",
router: rt,
data() {
return {
username: '',
tableData: [],
employeesData: [],
page:{
pageNum: 1,
pageSize: 10,
total:0,
},
search:{
searchName: '',
searchDate: '',
searchDateEnd:'',
},
departmentData: [],
dialogTableVisible: false,
}
},
methods: {
getUser() {
axios.post("/getUser").then(res => {
this.username=res.data
console.log(res.data)
})
},
getData(){
axios.post("/getData").then(res => {
this.tableData=res.data.data
console.log(this.tableData)
})
},
getEmployees(){
axios.get("/getEmployees",{
params:{
pageNum:this.page.pageNum,
pageSize:this.page.pageSize,
searchName:this.search.searchName,
searchDate:this.search.searchDate,
searchDateEnd:this.search.searchDateEnd,
}
}).then(res => {
this.employeesData=res.data.data.list;
this.page.pageNum=res.data.data.pageNum;
this.page.pageSize=res.data.data.pageSize;
this.page.total=res.data.data.total;
console.log(res.data.data)
})
},
getDepartments(){
this.$router.push("departments");
},
tableRowClassName({row, rowIndex}) {
if (rowIndex === 0) {
return 'success-row';
} else if (rowIndex === 1) {
return 'warning-row';
}
return '';
},
handleSizeChange(val) {
this.page.pageSize=val;
this.getEmployees();
console.log(`每页 ${val} 条`);
},
handleCurrentChange(val) {
this.page.pageNum=val;
this.getEmployees();
console.log(`当前页: ${val}`);
},
getDepartmentInfo(depId){
console.log(depId);
axios.get("/getDepartmentById",{
params:{
depId:depId,
}
}).then(res => {
this.departmentData=res.data.data;
console.log(this.departmentData)
this.dialogTableVisible = true;
})
}
},
created: function () {
this.getUser(),
this.getEmployees()
},
watch: {
'$route' (to, from) {
this.$router.go(0);
}
},
})
</script>
</body>
<style>
.el-table .warning-row {
background: oldlace;
}
.el-table .success-row {
background: #f0f9eb;
}
.el-container {
padding: 0px;
margin: 0px;
height: 100%;
}
.item{
margin-left: 200px;
}
</style>
</html>
页面效果:
8.接收getEmployees请求
controller: 传入分页参数和条件查询参数,调用service处理。
@GetMapping("/getEmployees")
public R findEmployees(@RequestParam Map<String, Object>params) throws ParseException {
System.out.println(params);
PageInfo<EmployeeVo> employees = empService.findEmployees(params);
return R.ok().setData(employees);
}
service: 使用PageHelper进行分页查询,调用mapper方法进行查询
@Override
public PageInfo<EmployeeVo> findEmployees(Map<String, Object>params) throws ParseException {
PageHelper.startPage(params);
PageInfo<EmployeeVo> employeesPageInfo = new PageInfo<>(empMapper.findEmployees(params));
return employeesPageInfo;
}
mapper:
List<EmployeeVo> findEmployees(@Param("params") Map<String, Object> params);
xml:
<select id="findEmployees" resultType="com.lzp.vo.EmployeeVo">
select e.*,d.department_name from employees e
LEFT JOIN departments d
ON d.`department_id`=e.`department_id`
where 1=1
<if test=" params.searchName != null and params.searchName!='' ">
and ( first_name like "%"#{params.searchName}"%" or
last_name like "%"#{params.searchName}"%" )
</if>
<if test=" params.searchDate != null and params.searchDate!='' ">
and hire_date <![CDATA[ > ]]> #{params.searchDate}
</if>
<if test=" params.searchDateEnd != null and params.searchDateEnd!='' ">
and hire_date <![CDATA[ < ]]> #{params.searchDateEnd}
</if>
</select>
9.处理部门信息
点击部门信息,根据department_id查询部门信息 controller: 根据部门id查询
@GetMapping("/getDepartmentById")
public R findDepartmentById(@RequestParam("depId") Integer depId) throws ParseException {
System.out.println(depId);
List<DepatmentVo> depatmentVo = empService.findDepartmentById(depId);
return R.ok().setData(depatmentVo);
}
service: 根据id查询
@Override
public List<DepatmentVo> findDepartmentById(Integer depId) {
List<DepatmentVo> departmentById = empMapper.findDepartmentById(depId);
return departmentById;
}
mapper:
List<DepatmentVo> findDepartmentById(@Param("depId") Integer depId);
xml:
<select id="findDepartmentById" resultType="com.lzp.vo.DepatmentVo">
select department_id,department_name from departments where department_id=#{depId}
</select>
10.查询所有部门
点击部门总览,查询部门总信息,进行页面跳转。 页面效果:
页面内容:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>departments</title>
<script src="/vue/vue.js" rel="stylesheet" type="text/javascript"></script>
<script src="/element-ui/lib/index.js"></script>
<link rel="stylesheet" href="/element-ui/lib/theme-chalk/index.css">
<script src="/axios/axios.min.js"></script>
<script src="/vue-router/vue-router.min.js"></script>
</head>
<body>
<div id="department" class="department">
<h3 align="right" >你好:{{username}}</h3>
<div>
<el-button size="small" type="primary" round @click="getLocationInfo()" icon="el-icon-menu">
地址总览
</el-button>
</div>
<h3 style="color: #a0cfff" align="left" >
部门信息展示
</h3>
<template>
<el-table
:data="departmentsData"
style="width: 100%"
:row-class-name="tableRowClassName">
<el-table-column
prop="departmentId"
label="部门编号"
width="180">
</el-table-column>
<el-table-column
prop="departmentName"
label="部门名称"
width="180">
</el-table-column>
<el-table-column
prop="managerName"
label="部门领导"
width="180">
</el-table-column>
<el-table-column
prop="streetAddress"
label="部门地址"
width="180">
</el-table-column>
<el-table-column>
<template slot-scope="scope">
<el-button type="text" @click="getLocationInfo(scope.row.locationId)">地址信息
</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog title="地址信息" :visible.sync="dialogTableVisible" class="pop-up">
<el-table :data="locationsData">
<el-table-column prop="locationId" label="地址编号" width="150"></el-table-column>
<el-table-column prop="streetAddress" label="部门地址" width="200"></el-table-column>
<el-table-column prop="postalCode" label="邮编" width="150"></el-table-column>
<el-table-column prop="city" label="所在城市" width="150"></el-table-column>
<el-table-column prop="stateProvince" label="所在州省" width="150"></el-table-column>
</el-table>
</el-dialog>
<div class="block">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="page.pageNum"
:page-sizes="[5, 10, 15, 20,25,30,35,40,45,50]"
:page-size="page.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="page.total">
</el-pagination>
</div>
</template>
</div>
<script>
<!-- 创建vue实例-->
new Vue({
el:"#department",
data() {
return{
username:'',
departmentsData: [],
locationsData: [],
dialogTableVisible: false,
page:{
pageNum: 1,
pageSize: 10,
total:0,
},
}
},
methods:{
tableRowClassName({row, rowIndex}) {
if (rowIndex === 0) {
return 'success-row';
} else if (rowIndex === 1) {
return 'warning-row';
}
return '';
},
handleSizeChange(val) {
this.page.pageSize=val;
this.getDepartments();
console.log(`每页 ${val} 条`);
},
handleCurrentChange(val) {
this.page.pageNum=val;
this.getDepartments();
console.log(`当前页: ${val}`);
},
getUser() {
axios.post("/getUser").then(res => {
this.username=res.data
console.log(res.data)
})
},
getDepartments(){
axios.get("/getDepartments",{
params:{
pageNum:this.page.pageNum,
pageSize:this.page.pageSize,
}
}).then(res => {
this.departmentsData=res.data.data.list;
this.page.pageNum=res.data.data.pageNum;
this.page.pageSize=res.data.data.pageSize;
this.page.total=res.data.data.total;
console.log(res.data.data)
})
},
getLocationInfo(locationId){
console.log(locationId);
axios.get("/getLocations",{
params:{
locationId:locationId,
}
}).then(res => {
this.locationsData=res.data.data;
console.log(this.locationsData)
this.dialogTableVisible = true;
})
}
},
created: function () {
this.getUser(),
this.getDepartments()
},
})
</script>
</body>
<style>
.el-table .warning-row {
background: oldlace;
}
.el-table .success-row {
background: #f0f9eb;
}
.department{
margin-left: 200px;
}
.pop-up .el-dialog__body {
padding: 3px 30px;
overflow-y: auto;
height: 40vh;
}
</style>
</html>
11.接收getDepartments请求
controller: 传入分页参数,调用service方法查询
@GetMapping("/getDepartments")
@ResponseBody
public R getDepartments(@RequestParam Map<String, Object> params ){
PageInfo<DepartmentsVo> departments = depService.getDepartments(params);
return R.ok().setData(departments);
}
service: 分页查询查出所有部门信息,遍历集合,根据manager_id查询领导名字。返回PageInfo。
@Override
public PageInfo<DepartmentsVo> getDepartments(Map<String, Object>params) {
PageHelper.startPage(params);
PageInfo<DepartmentsVo> departmentsVoPageInfo = new PageInfo<>(depMapper.getDepartments(params));
List<DepartmentsVo> departmentsVoList = departmentsVoPageInfo.getList().stream().map((item) -> {
String managerNameById = empMapper.findManagerNameById(item.getManagerId());
item.setManagerName(managerNameById);
return item;
}).collect(Collectors.toList());
departmentsVoPageInfo.setList(departmentsVoList);
return departmentsVoPageInfo;
}
mapper:
List<DepartmentsVo> getDepartments(@Param("params") Map<String, Object> params);
String findManagerNameById(@Param("id") Integer id);
xml:
<select id="getDepartments" resultType="com.lzp.vo.DepartmentsVo">
SELECT d.*,l.street_address
FROM departments d
LEFT JOIN locations l
ON d.`location_id`=l.`location_id`
</select>
<select id="findManagerNameById" resultType="String">
select first_name from employees where employee_id=#{id}
</select>
这里也可以使用两次关联查询
SELECT d.`department_id`,d.`department_name`,e.`first_name`,l.street_address
FROM departments d
LEFT JOIN employees e
ON d.`manager_id`=e.`employee_id`
LEFT JOIN locations l
ON d.`location_id`=l.`location_id`
12.地址信息和地址总览
页面效果: 地址信息: 地址总览: 这里都采用了弹窗,未新增页面。 两个查询方式采用同一个方法接收,有id的话根据id查询,没有的话查询全部 controller:
@GetMapping("/getLocations")
public R getLocations(@RequestParam(required = false)Integer locationId){
System.out.println(locationId);
List<Location> locations = localService.getLocations(locationId);
return R.ok().setData(locations);
}
service:
@Override
public List<Location> getLocations(Integer locationId) {
return localMapper.getLocations(locationId);
}
mapper:
List<Location> getLocations(@Param("locationId") Integer locationId);
xml:
<select id="getLocations" resultType="com.lzp.entity.Location">
select location_id,street_address,postal_code,city,state_province from locations
where 1=1
<if test=" locationId > 0 ">
and location_id=#{locationId}
</if>
</select>
至此,就完成了简单的vue整合
2.项目部署的问题
1.内存占用问题
想把项目部署到服务器,服务器内存所剩不多,引出第一个问题,怎么确定这个程序占用多大内存呢? 默认情况:最大内存为物理内存的1/4,初始内存为物理内存的1/64,这个内存指的是堆内存,由新生代和老年代两部分,比例为1:2,所以我的远程服务器内存为8g,最大内存就会占2g。所以启动时要指定内存,先设置了256m看下情况。 metaspace为元空间大小,默认为20M左右,需要增大,不然会进行fullGC. 启动程序,打开jvisualvm.exe(在jdk的bin目录下)
可以看到总数为256m。 这里看出内存使用量,可以看出新生代使用较多,老年代使用很少,所以可以确定256m内存是完全满足要求的(128m应该也能满足–)。
至此,困扰的问题就大概解决了,这个小项目的内存占用为100m左右。当然这是没有请求进来的情况下,youngGC的频率不高,大概几十秒一次(在linux下频率还要更低)。
这是在idea打开的结果,采用jar包部署的方式,新生代内存增加没这么快 下面是采用jar包部署的方式(windows) linux 更为平缓~
2.jmeter测试
测试1s10线程,每秒进行5次youngGC。 这里是在windows部署的项目,又尝试在阿里云部署,然后做了jmeter测试,发现GC频率并不高。这是windows和linux的区别么,有待大神指教-----
生成了jmeter文件,数据有点恐怖,最长的响应时间为15s。。。,感觉应该和本地网络有很大关系,需要再做测试。(网络良好的时候又做了一次测试,每秒线程数为25时,最大响应时间在1000ms以内,每秒线程数为30时,最大响应时间就是秒级别的了,所以可以确定,在这个256m内存配置下,网络条件良好的情况下,程序最多每秒可以处理25次请求)
jmeter测试方式 在软件中设置好参数,添加好请求,然后保存测试文件 然后进到jmeter的bin目录,打开cmd 执行 jmeter -n -t HTTP请求2.jmx -l testplan/result/result.txt -e -o testplan/webreport
- HTTP请求2.jmx 测试文件的名字,我这里在bin目录下,可以单独指定文件夹
- testplan/result 请求执行记录的文件夹
- testplan/webreport 生成html报告的文件夹
3.VisualVM监控阿里云服务器部署的项目
1.linux服务器配置
1.在java bin目录下新建文件 jstatd.all.policy 文件内容: grant codebase “file:/home/mysoftware/myjava/jdk1.8.0_171/lib/tools.jar” { permission java.security.AllPermission; }; 2.然后启动jstatd nohup jstatd -J-Djava.security.policy=/home/mysoftware/myjava/jdk1.8.0_171/bin/jstatd.all.policy -J-Djava.rmi.server.hostname=47.98.xxx.xxx &
3.然后netstat -lutnp |grep jstatd 查看jstad占用的端口(有两个) 防火墙添加
4.然后在xshell就可以使用 jps jmap 等命令了
2.windows下配置
打开VisualVM,添加远程主机(在jdk的bin目录下) 总结: jvm方面的知识还需要提高,加油!
|