项目需求:一个客户管理系统、制作单生成、报价表(xls)生成及下载
后端:springboot、springSecurity、Mybatis-Plus、devtools、thymeleaf、poi、lombox、swagger2
前端:vue、elementUI
采用前后端分离开发,实装将前端build的文件复制到后端 static 下,使用maven打包成一个单独的jar文件,放到服务器上双击即可运行。
一、后端知识点:
1.pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="#34; xmlns:xsi="#34;
xsi:schemaLocation=" #34;>
<modelVersion>4.0.0</modelVersion>
<groupId>icu.woodlum.yderp</groupId>
<artifactId>demo</artifactId>
<version>2.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.0.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--安全框架-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--模板-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis-plus框架-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<!--热启动-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--xlsx生成依赖包-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.0.1</version>
</dependency>
<!-- -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<!--实体类自动get set-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
< Configuration >
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
< execution s>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
pom.xml中有个要点:使用maven>package打包jar一直不成功,加上executions中的内容就好了
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
2.session缓存时间
server.servlet.session.timeout=PT9H
Duration转换字符串方式,默认为正,负以-开头,紧接着P,以下字母不区分大小写
D :天; T:天和小时之间的分隔符 H :小时 M:分钟 S:秒 每个单位都必须是数字,且时分秒顺序不能乱
比如P2dt3m5s P3d pt3h
3.开启跨域请求
@Configuration
public class CorsConfig {
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*"); // 允许任何域名使用
corsConfiguration.addAllowedHeader("*"); // 允许任何头
corsConfiguration.addAllowedMethod("*"); // 允许任何方法(post、get等)
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig()); // 对接口配置跨域设置
return new CorsFilter(source);
}
}
4.mybatis-plus相关
a.开启分页
@Configuration
public class MybatisConfig {
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
}
b.数据库字段自动填充配置
@Configuration
public class MyMetoObjectHandle implements MetaObjectHandler {
@ Override
public void insertFill(MetaObject metaObject) {
setFieldValByName("createTime", LocalDateTime.now(),metaObject);
setFieldValByName("updateTime",LocalDateTime.now(),metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
setFieldValByName("createTime", LocalDateTime.now(),metaObject);
setFieldValByName("updateTime",LocalDateTime.now(),metaObject);
}
}
实体类字段中
//自动填充时间
@TableField(value = "createTime",fill = FieldFill.INSERT_UPDATE)
private LocalDateTime createTime;
//此属性在表中不存在
@TableField(exist = false )
private Customer customer;
c.mapper接口
@Mapper
@Component
public interface CustomerMapper extends BaseMapper<Customer> {
@Results({@Result(column = "tId",property = "tId"),
@Result(column = "tId",property = "tax",
one = @One( Select = "icu.woodlum.yderp. dao .TaxMapper.selectById"))})
@Select("select * from customer ${ew.customSqlSegment} ")
Page<Customer> selectCustomerPage(IPage<Customer> page,@Param(Constants.WRAPPER)
Wrapper<Customer> wrapper);
@Results({@Result(column = "tId",property = "tId"),
@Result(column = "tId",property = "tax",
one = @One(select = "icu.woodlum.yderp.dao.TaxMapper.selectById"))})
@Select("select * from customer ${ew.customSqlSegment} ")
Customer selecCustomertOne(@Param(Constants.WRAPPER) Wrapper<Customer> wrapper);
@Results({@Result(column = "tId",property = "tId"),
@Result(column = "tId",property = "tax",
one = @One(select = "icu.woodlum.yderp.dao.TaxMapper.selectById"))})
@Select("select * from customer where id = #{id}")
Customer selecCustomertById(Serializable id);
}
5.中间表的更新,先删除中间表中相应的数据再作insert
6.结果集封装
@Data
public class Result<T> {
private T data;
private boolean success ;
private int code;
private String message ;
private Result() {
}
private Result(T data, boolean success, int code, String message) {
this.data = data;
this.success = success;
this.code = code;
this.message = message;
}
private Result(boolean success, int code, String message) {
this.success = success;
this.code = code;
this.message = message;
}
public static <T> Result<T> newInstance(){
return new Result<T>();
}
// 成功
public static <T> Result<T> defaultSuccess(T data){
return new Result<T>(data, true, 200, "返回成功");
}
// 失败
public static <T> Result<T> defaultFailure(){
return new Result<T>(false, 500, "系统内部错误");
}
// 自定义失败一
public static <T> Result<T> failure(T data, int code, String message){
return new Result<T>(data, false, code, message);
}
// 自定义失败二
public static <T> Result<T> failure(int code, String message){
return new Result<T>(false, code, message);
}
}
7.安全相关
a.User类,实现UserDetails接口
@Data
public class User implements UserDetails {
private int id;
// 用户名为工号
private String username;
private String realname;
private String password;
@TableField("createDate")
private LocalDate createDate;
@TableField("lastLoginTime")
private LocalDateTime lastLoginTime;
//账户是否不可用,数据库为1代表true,0代表false,默认为1
private boolean enabled;
//账户是否未过期
@TableField("accountNonExpired")
private boolean accountNonExpired;
//账户是否锁定
@TableField("accountNonLocked")
private boolean accountNonLocked;
//密码是否未过期
@TableField("credentialsNonExpired")
private boolean credentialsNonExpired;
//权限集合
@TableField(exist = false)
private List<GrantedAuthority> authorities;
}
b.用户dao接口
@Mapper
@Component
public interface UserDao extends BaseMapper<User> {
//用户名查询用户信息
@Select("select * from user where username = #{userName}")
public User selectByUserName(String userName);
//用户名查询当前用户的权限信息
@Select("select permission.* FROM" +
" user u" +
" INNER JOIN user_role ON u.id = user_role.uid" +
" INNER JOIN role_permission on user_role.rid = role_permission.rid" +
" INNER JOIN permission on role_permission.pid = permission.id" +
" WHERE u.username = #{userName};")
public List<Permission> findPermissionByUserName(String userName);
//注册用户
@Insert("insert into user values(default,#{user.username}," +
"#{user.realname},#{user.password},now(),now(),default,default,default,default)")
int insert(@Param("user") User user);
}
c.用户服务接口
@Service
public class MyUserDetalisService implements UserDetailsService {
private UserDao userDao;
public MyUserDetalisService(UserDao userDao) {
this.userDao = userDao;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询当前用户信息
User user = userDao.selectByUserName(username);
if (user == null){
throw new RuntimeException("没有用户");
}
//查询当前用户权限
List<Permission> list = userDao.findPermissionByUserName(username);
List<GrantedAuthority> authorities = new ArrayList<>();
/*
* 将getPermTag()构建一个GrantedAuthority接口实例对象,放于List<GrantedAuthority>中
* */ list.forEach(l ->{
authorities.add(new SimpleGrantedAuthority(l.getPermTag()));
});
//用户信息设置权限集合
user.setAuthorities(authorities);
return user;
}
}
d.security配置
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
private MyUserDetalisService myUserDetalisService;
public MySecurityConfig(MyUserDetalisService myUserDetalisService) {
this.myUserDetalisService = myUserDetalisService;
}
//http安全配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //所有请求拦截
.antMatchers("/static/**").permitAll() //放在所有拦截的前面放行不需要拦截的资源
.antMatchers("/login").permitAll() //放行登录
.antMatchers("/logout").permitAll() //放行注销
.antMatchers("/reg").hasAnyAuthority("REGUSER")//此页需要权限
.anyRequest().authenticated() //除上所有拦截需要用户认证
.and()
.formLogin().loginPage("/login") //forlogin认证
.failureUrl("/login?error=true")//登陆错误页
.and()
.csrf().disable(); //关闭csrf校验
}
//认证管理器配置
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetalisService)
//查询时需要密码加密后和数据库做比较
.passwordEncoder(new BCryptPasswordEncoder());
}
}
e.403权限配置
@Configuration
public class ErrorPageConfig implements ErrorPageRegistrar {
@Override
public void registerErrorPages(ErrorPage registry registry) {
//权限不足的页面导向
ErrorPage error403 = new ErrorPage(HttpStatus.FORBIDDEN,"/403");
registry.addErrorPages(error403);
}
}
8.springMVC配置,其中缓存静态资源,加快访问速度
@Configuration
public class MyConfig implements WebMvcConfigurer {
//静态资源URI和位置映射
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
//设置静态资源缓存1年
.setCachePeriod(3153600);
}
//视图映射
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/login").setViewName("login");
registry.addViewController("/403").setViewName("403");
}
}
9.swagger配置
@Configuration
public class Swagger2 {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("icu.woodlum.yderp.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("yderp-api文档")
.description("yderp-api文档")
.termsOfServiceUrl("#34;)
.version("2.0")
.build();
}
}
10.favicon.ico图标配置
原则上此图片放置于static文件夹下即可,但是某些情况下可能不会显示,因此要在html中指定
<link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico">
二、前端知识点
1.前端采用的vue-cli脚手架构建,注意安装router,ESLint会强制要求代码格式
npm install -g cnpm --registry=
cnpm install webpack -g
cnpm install vue-cli -g
//
vue init webpack 项目名称
cnpm install
启动命令:npm run dev,关闭Ctrl+C
打包编译:npm run build
2.router路由:router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import customerheadButton from '../components/cutomer/CustomerHeadButton'
import customerMain from '../components/cutomer/CustomerMain'
Vue.use(VueRouter)
export default new VueRouter({
// mode去除浏览器中的#号
mode: 'history',
routes: [
{
path: '/',
component: main,
children: [
{
path: 'file-customer',
components: {
'head-button': customerheadButton,
'tax-main': customerMain
},
meta: {
title: '客户档案'
}
}
]
}
]
})
3.Global.vue,全局配置后端接口地址
<script>
const BASE_URL = '#39;
//const BASE_URL = '/'
export default {
BASE_URL
}
</script>
import global from '../components/Global.vue'
axios.post(global.BASE_URL + 'customer/selectList', {'name': params.searchName, 'taxName': params.searchTaxName, 'currentPage': params.currentPage, 'perPages': params.pageSize})
.then(function (res) {
state .totalpages = res.data.data.total
state.customerlist = res.data.data.records
})
4.title设置
// title设置
router.beforeEach((to, from, next) => {
// 在路由进入之前判断是否有标题
if (to.meta.title) {
document.title = to.meta.title
}
// 继续执行路由
next()
})
5.vuex
a.index.js
import Vue from 'vue'
import Vuex from 'vuex'
import tax from './taxModule'
import customer from './CustomerModule'
import product from './ProductModule'
import designNotice from './DesignNoticeModule'
import price from './PriceModule'
// 挂载Vuex
Vue.use(Vuex)
// 创建VueX对象
export default new Vuex.Store({
modules: {
tax, customer, product, designNotice, price
}
})
b.CustomerModule.js
import Vue from 'vue'
import axios from 'axios'
import global from '../components/Global.vue'
Vue.use(axios)
export default {
// customer模块状态,必须开启命名空间
namespaced: true,
state: {
// customerInfo组件显示状态
customerFormVisible: false,
....略
},
mutations: {
// 注意多参数传递,参数是一个对象
addcustomerForm (state, params) {
state.customerFormTitle = params.customerFormTitle
state.customerFormOkTitle = params.customerFormOkTitle
state.customerFormInfo = params.customerFormInfo
state.customerFormVisible = true
},
...略
}
c.使用
computed: {
...mapState('customer', ['customerFormVisible']),
...mapState('tax', ['taxlist'])
},
methods: {
// 方法注册
...mapMutations('customer', ['customerFormCancel']),
...mapMutations('tax', ['getTaxListAll']),
6.element分页使用
<!--分页组件-->
<div class="block" style="display: flex">
<el-pagination
:background="true"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
@next-click="currentPage += 1"
@pre-click="currentPage -= 1"
:current-page="currentPage"
:page-sizes="[10, 20]"
:page-size="pagesize"
layout="total, sizes, prev, pager, next, jumper"
:total="totalpages">
</el-pagination>
</div>
export default {
data () {
return {
// 默认当前为第1页
currentPage: 1,
// 默认每页10行
pagesize: 10
}
},
watch: {
// 监听vuex中的标志
vuexCurrentPage (newVal) {
if (newVal === 1) {
this.currentPage = 1
}
},
// 监听vuex中的每页数目
vuexPageSizes (newVal) {
if (newVal === 10) {
this.pagesize = 10
}
}
},
methods: {
// 每页数变化时
handleSizeChange (val) {
this.pagesize = val
var params = {searchName: this.searchName, searchTaxName: this.searchTaxName, currentPage: this.currentPage, pageSize: this.pagesize}
this.getCustomerListAll(params)
// 清除vuex看每页标志
this.clearDefaultPageSize()
},
// 当前页变化时
handleCurrentChange (val) {
this.currentPage = val
var params = {searchName: this.searchName, searchTaxName: this.searchTaxName, currentPage: this.currentPage, pageSize: this.pagesize}
this.getCustomerListAll(params)
// 清除vuex中的当前页标志
this.clearDefaultCurrentPage()
}
}
vuex:
// 以下两个相当于标记符flag
vuexCurrentPage: -1,
vuexPageSizes: -1
// 清除默认
clearDefaultPageSize (state) {
state.vuexPageSizes = -1
},
// 清除vuexCurrengPage
clearDefaultCurrentPage (state) {
state.vuexCurrentPage = -1
},
7.打开新的一页
<router-link target="_blank" :to="{path:'/showNotice?id='+scope.row.id}">
<el-button type="success" size="mini">查看</el-button>
</router-link>
//router.js中
{path: '/showNotice',
component: showNotice,
meta: {
title: '设计制作通知单'
}
}
//showNotice.vue中
created () {
var _this = this
this.$axios.post('designNotice/selectById', {id: this.$route.query.id}).then(function (res) {
if (res.data.success === true) {
_this.notice = res.data.data
}
})
}