前言:归因于学校的工程实践,要求小组完成一个项目,包括项目的开题报告、需求分析、数据库设计、概要设计、原型设计、详细设计、项目编码、功能测试、项目集成。作为组长,我想先为项目搭好整体的框架,包括后端的模块划分、依赖版本统一、技术栈,前端界面布局设计以及整体的一个风格等等。并且团队内开发采用gitee进行版本控制,这样做的好处是方便最后的整合,节省开发时间,并且为以后大家在公司内开发奠定基础,以后可能就是一个人负责一个或几个模块。当然让我自己写一个框架肯定是不行的,我一开始的想法就是使用网上大佬写的开源框架,然后对里面的代码进行修改和删减,“变成”我们自己的。我认为不论是在接外包还是完成学校的一些项目任务,都是可以使用一些开源框架的,比如易优、人人开源等,在修改和改错的过程中也是能学到技巧和知识的。多的不说,我就拿本次实践项目为例子从头开始搭建。
项目设计到vue和Spring Cloud技术,请提前安装Node和nacos
效果展示
选择模板
这次我就选择人人开源作为模板,我会使用到它的renren-fast-vue和renren-fast,其他的大家有兴趣可以点击去看看,特别是renren-generator——代码生成器,自动生成前和后端的代码,除了一般的controller层、service层等一些后端代码,还有对应的vue代码。
测试后端
将renren-fas下载或者git拉取都可,打开项目,导入相关依赖。 根据db文件夹下的文件创建数据库,是MySQL就是拿MySQL,其他拿其他。 修改项目配置文件 启动没问题说明后端代码和环境没问题
测试前端
初次打开项目会提示npm install 下载相干依赖,先别点击,先修改package.json 文件中的个别依赖版本号,避免某些依赖因为版本号冲突导不进去。
将原来的dependencies部分用下面的代码替换
"dependencies": {
"axios": "0.17.1",
"babel-plugin-component": "0.10.1",
"babel-polyfill": "6.26.0",
"element-ui": "^2.8.2",
"gulp": "4.0.2",
"gulp-concat": "2.6.1",
"gulp-load-plugins": "2.0.5",
"gulp-replace": "1.0.0",
"gulp-shell": "0.8.0",
"lodash": "4.17.5",
"node-sass": "^6.0.0",
"npm": "^6.9.0",
"pubsub-js": "^1.8.0",
"sass-loader": "6.0.6",
"svg-sprite-loader": "3.7.3",
"vue": "2.5.16",
"vue-cookie": "1.1.4",
"vue-router": "3.0.1",
"vuex": "3.0.1"
},
替换完后,再点击npm install 。 只要控制台中没有出现error ,说明依赖全部下载完成,控制台中输入npm run dev 运行 由于我搭建的团队框架中只使用了项目中的菜单管理模块,并且我删除按钮的操作,目录的添加相当于一整个大模块,而菜单则是模块下的各个服务,添加成功后我们只需要在它的项目文件夹view下的modules创建目录(可参照它的项目结构),这符合项目的模块化开发,我觉得这一块对于我们开发vue的开发节省了较多的时间,比较方便,其他功能后面需要可以参照它的后端代码实现,大家根据需求适当删减。
创建后端项目
这是我们组决定的项目以及模块划分,大家可根据自身情况创建。
服务模块划分
首先创建一个maven 空项目作为父项目,作为聚合 接着在父项目下创建各个模块(服务),举个例子:
tips:版本号后期可以通过pom进行修改,因为我们项目是分模块开发的,也就是各个服务分开,后端的端口可能很多,所以这里使用到了Spring Cloud相关依赖,如果是单模块(后端只有一个端口),完全不用导入Spring Cloud相关依赖。
整个后端项目结构: 搭建目前我只用到了4个模块:
- common(公共)
- gateway(网关)
- admin(登录)
- system(菜单管理)
也可以像人人开源一样,将登录和菜单管理放在一个模块。大家搭建的时候创建这4个模块即可。 除了gateway模块,其他3个模块里面的内容基本和renren-fast中的差不多,不同的是删除了登录时需要的验证码、用户权限,下面我将改好的部分代码贴出,大家根据结构复制renren-fast中的代码即可。涉及到的数据库表只有sys_admin和sys_menu,可以单独创建一个数据库将这两张表的数据和结构复制到新数据库中
common
存放工具类,公共返回对象,全局异常等相关类和工具包
结构
pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>zuke</artifactId>
<groupId>com.kt.zuke</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<description>存放工具类,公共返回对象,全局异常等相关类和工具包</description>
<artifactId>zuke-common</artifactId>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.24</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.2.1</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.0.Final</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
以下皆与renren-fast中对应类或接口相同,大家复制即可。
GlobalException.class
Constant.class
PageUtil.class
Result.class
AddGroup.interface
UpdateGroup.interface
gateway
需要在启动类ZukeGatewayApplication.class中加上@EnableDiscoveryClient注解 结构:
pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.cuit.zuke</groupId>
<artifactId>zuke-gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>zuke-gateway</name>
<description>API网关</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>com.kt.emall</groupId>
<artifactId>emall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
CrosConfig
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsWebFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.setAllowCredentials(true);
source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsWebFilter(source);
}
}
application
server:
port: 9000
spring:
application:
name: zuke-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
routes:
- id: zuke-system
uri: lb://zuke-system
predicates:
- Path=/zukeApi/sys/**
filters:
- RewritePath=/zukeApi/(?<segment>.*),/$\{segment}
- id: zuke-admin
uri: lb://zuke-admin
predicates:
- Path=/zukeApi/admin/**
filters:
- RewritePath=/zukeApi/(?<segment>.*),/$\{segment}
admin
需要在启动类ZukeAdminApplication.class中加上@EnableDiscoveryClient注解开启服务注册功能,将服务注册到注册中心 结构:
AdminController
@RestController
@RequestMapping("/admin")
public class AdminController extends AbstractController{
@Autowired
AdminService adminService;
@GetMapping("/list")
public Result list (@RequestParam Map<String, Object> params) {
return Result.ok().put("page", adminService.queryPage(params));
}
@GetMapping("/info")
public Result info(){
return Result.ok().put("user", getAdmin());
}
}
LoginController
@RestController
@RequestMapping("/admin")
public class LoginController {
@Autowired
private AdminService adminService;
@Autowired
private TokenService tokenService;
@PostMapping("/login")
public Map<String, Object> login(@RequestBody LoginVO loginVO) {
AdminEntity admin = adminService.queryByUserName(loginVO.getUsername());
if(admin == null || !admin.getPassword().equals(new Sha256Hash(loginVO.getPassword(), admin.getSalt()).toHex())) {
return Result.error("账号或密码不正确");
}
if(admin.getStatus() == 0){
return Result.error("账号已被锁定,请联系管理员");
}
return tokenService.createToken(admin.getUserId());
}
}
LoginVO
@Data
public class LoginVO {
private String username;
private String password;
private String uuid;
}
application
server:
port: 9001
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/zuke_admin?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
hikari:
pool-name: DateHikariCP
minimum-idle: 5
idle-timeout: 180000
maximum-pool-size: 10
auto-commit: true
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1
application:
name: zuke-admin
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
global-config:
db-config:
id-type: AUTO
logic-delete-value: -1
logic-not-delete-value: 0
banner: false
system
需要在启动类ZukeSystemApplication.class中加上@EnableDiscoveryClient注解开启服务注册功能,将服务注册到注册中心 结构:
pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.cuit.system</groupId>
<artifactId>zuke-system</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>zuke-system</name>
<description>系统设置</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>com.kt.zuke</groupId>
<artifactId>zuke-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
SysMenuController
@RestController
@RequestMapping("/sys/menu")
public class SysMenuController {
@Autowired
private SysMenuService sysMenuService;
@GetMapping("/nav")
public Result nav() {
List<SysMenuEntity> menuList = sysMenuService.getNavMenu();
return Result.ok().put("menuList", menuList);
}
@GetMapping("/list")
public List<SysMenuEntity> list() {
List<SysMenuEntity> menuList = sysMenuService.list();
HashMap<Long, SysMenuEntity> menuMap = new HashMap<>(12);
for (SysMenuEntity s : menuList) {
menuMap.put(s.getMenuId(), s);
}
for (SysMenuEntity s : menuList) {
SysMenuEntity parent = menuMap.get(s.getParentId());
if (Objects.nonNull(parent)) {
s.setParentName(parent.getName());
}
}
return menuList;
}
@GetMapping("/select")
public Result select(){
List<SysMenuEntity> menuList = sysMenuService.queryNotButtonList();
SysMenuEntity root = new SysMenuEntity();
root.setMenuId(0L);
root.setName("一级菜单");
root.setParentId(-1L);
root.setOpen(true);
menuList.add(root);
return Result.ok().put("menuList", menuList);
}
@GetMapping("/info/{menuId}")
public Result info(@PathVariable("menuId") Long menuId) {
SysMenuEntity menu = sysMenuService.getById(menuId);
return Result.ok().put("menu", menu);
}
@PostMapping("/save")
public Result save(@RequestBody SysMenuEntity menu) {
verifyForm(menu);
sysMenuService.save(menu);
return Result.ok();
}
@PostMapping("/update")
public Result update(@RequestBody SysMenuEntity menu) {
verifyForm(menu);
sysMenuService.updateById(menu);
return Result.ok();
}
@PostMapping("/delete/{menuId}")
public Result delete(@PathVariable("menuId") long menuId){
if(menuId == 1){
return Result.error("系统菜单,不能删除");
}
List<SysMenuEntity> menuList = sysMenuService.queryListParentId(menuId);
if(menuList.size() > 0){
return Result.error("请先删除子菜单或按钮");
}
sysMenuService.delete(menuId);
return Result.ok();
}
private void verifyForm(SysMenuEntity menu) {
if (StringUtils.isBlank(menu.getName())) {
throw new GlobalException("菜单名称不能为空");
}
if (menu.getParentId() == null) {
throw new GlobalException("上级菜单不能为空");
}
if (menu.getType() == Constant.MenuType.MENU.getValue()) {
if (StringUtils.isBlank(menu.getUrl())) {
throw new GlobalException("菜单URL不能为空");
}
}
int parentType = Constant.MenuType.CATALOG.getValue();
if (menu.getParentId() != 0) {
SysMenuEntity parentMenu = sysMenuService.getById(menu.getParentId());
parentType = parentMenu.getType();
}
if (menu.getType() == Constant.MenuType.CATALOG.getValue() ||
menu.getType() == Constant.MenuType.MENU.getValue()) {
if (parentType != Constant.MenuType.CATALOG.getValue()) {
throw new GlobalException("上级菜单只能为目录类型");
}
return;
}
if (menu.getType() == Constant.MenuType.BUTTON.getValue()) {
if (parentType != Constant.MenuType.MENU.getValue()) {
throw new GlobalException("上级菜单只能为菜单类型");
}
}
}
}
SysMenuDao
@Mapper
public interface SysMenuDao extends BaseMapper<SysMenuEntity> {
List<SysMenuEntity> queryListParentId(Long parentId);
List<SysMenuEntity> queryNotButtonList();
}
SysMenuEntity
@Data
@TableName(value = "sys_menu")
public class SysMenuEntity implements Serializable {
private static final long serialVersionUID = 1L;
...
@TableField(exist=false)
private List<SysMenuEntity> list=new ArrayList<>();
}
SysMenuService
SysMenuServiceImpl
@Service("sysMenuService")
public class SysMenuServiceImpl extends ServiceImpl<SysMenuDao, SysMenuEntity> implements SysMenuService {
/**
* 获取导航菜单
*
* @return 树形输出所有
*/
@Override
public List<SysMenuEntity> getNavMenu() {
// 所有菜单
List<SysMenuEntity> sysMenuEntities = baseMapper.selectList(null);
// 获取所有父级菜单
List<SysMenuEntity> parentMenu = sysMenuEntities.stream().filter(sysMenu -> sysMenu.getParentId() == 0).collect(Collectors.toList());
// 封装成父子孙结构
List<SysMenuEntity> collect = parentMenu.stream().map((menu) -> {
menu.setList(getChildren(menu, sysMenuEntities));
return menu;
}).collect(Collectors.toList());
return collect;
}
/**
* 根据父菜单,查询子菜单
*
* @param parentId 父菜单ID
*/
@Override
public List<SysMenuEntity> queryListParentId(Long parentId) {
return baseMapper.queryListParentId(parentId);
}
/**
* 删除
*
* @param menuId 菜单id
*/
@Override
public void delete(Long menuId) {
baseMapper.deleteById(menuId);
}
/**
* 获取不包含按钮的菜单列表
*
* @return List<SysMenuEntity>
*/
@Override
public List<SysMenuEntity> queryNotButtonList() {
return baseMapper.queryNotButtonList();
}
/**
* 递归获取所有子节点
*
* @param sysMenuEntity
* @param sysMenuEntityList
* @return
*/
private List<SysMenuEntity> getChildren(SysMenuEntity sysMenuEntity, List<SysMenuEntity> sysMenuEntityList) {
List<SysMenuEntity> children = sysMenuEntityList.stream().filter(sysMenu -> {
return sysMenu.getParentId().equals(sysMenuEntity.getMenuId());
}).filter(sysMenu -> {
return sysMenu.getType() != 2;
}).map(sysMenu -> {
sysMenu.setList(getChildren(sysMenu, sysMenuEntityList));
return sysMenu;
}).collect(Collectors.toList());
return children;
}
}
最后将这个四个服务都启动,如果都没报错,说明后台搭建完成。
创建前端项目
创建一个vue项目,过程这里我就不演示了,我直接来说明一下renren-fast-vue中部分文件的作用。 首先是package.json,它的作用相当于pom,这一块需要修改成自己项目信息。 新建的项目仅需要这些依赖:
"dependencies": {
"axios": "0.17.1",
"babel-plugin-component": "0.10.1",
"babel-polyfill": "6.26.0",
"element-ui": "^2.15.6",
"lodash": "4.17.5",
"node-sass": "^6.0.0",
"pubsub-js": "^1.8.0",
"sass-loader": "6.0.6",
"svg-sprite-loader": "3.7.3",
"vue": "^2.5.2",
"vue-cookie": "1.1.4",
"vue-router": "3.0.1",
"vuex": "3.0.1"
},
index.js
;(function () {
window.SITE_CONFIG = {};
window.SITE_CONFIG['baseUrl'] = 'http://localhost:9000/zukeApi';
window.SITE_CONFIG['domain'] = './';
window.SITE_CONFIG['version'] = '';
window.SITE_CONFIG['cdnUrl'] = window.SITE_CONFIG.domain + window.SITE_CONFIG.version;
})();
前端项目获取
总结:后端整体采用了Mybatis-plus和Spring Boot、Spring Cloud那一套技术,对照renren-fast,我只采用了它的登录和菜单模块,并且删除了验证码和权限,保留了Token。而对于前端,除了login和menu模块以及一些公共视图模块,其他我都舍弃了。同样删除了大部分功能(权限、按钮相关),并且优化了添加目录后,左边导航栏不自动刷新问题。
|