Java CRM源码:带手机端小程序,Vue前端+Springboot后端+MySQL数据库
Java客户管理CRM源码带手机端和小程序,1.前端:Vue 2.后端:Springboot 3.数据库:MySQL 小程序是用UNIAPP开发的。 后台登录账户:adminadmin 功能介绍: 系统管理:部门管理、员工管理、角色管理、系统设置 客户管理:公海客户、合同管理、我的客户、我的合同、回款管理 审批管理:客户审批、合同审批、回款审批 业绩管理:业绩目标 产品管理:产品分类、产品管理 日历:我的日程 统计分析:公海客户分析、员工客户分析、客户跟进分析、合同金额分析、产品销售分析、客户销售分析

最近在折腾一套Java全栈客户管理系统,麻雀虽小五脏俱全。前后端分离架构,前端Vue全家桶配UniApp三端适配,后端SpringBoot稳如老狗,数据库MySQL简单直接。咱们边撸代码边唠嗑,看看这系统怎么转起来的。

Java客户管理CRM源码带手机端和小程序,1.前端:Vue 2.后端:Springboot 3.数据库:MySQL 小程序是用UNIAPP开发的。 后台登录账户:adminadmin 功能介绍: 系统管理:部门管理、员工管理、角色管理、系统设置 客户管理:公海客户、合同管理、我的客户、我的合同、回款管理 审批管理:客户审批、合同审批、回款审批 业绩管理:业绩目标 产品管理:产品分类、产品管理 日历:我的日程 统计分析:公海客户分析、员工客户分析、客户跟进分析、合同金额分析、产品销售分析、客户销售分析

先看权限验证这茬。后端用了SpringSecurity配JWT令牌,登录接口返回的token得带着后续请求。代码里这个JwtAuthFilter挺有意思:
public class JwtAuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
String authToken = token.substring(7);
String username = jwtUtil.getUsernameFromToken(authToken);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userService.loadUserByUsername(username);
if (jwtUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
chain.doFilter(request, response);
}
}
这货专门在请求头里掏token,验证通过后直接塞进SecurityContext。前端axios拦截器配个请求头自动带token,整套鉴权流程就闭环了。

说到客户公海池,这功能挺有CRM特色。后端用MyBatis-Plus的分页插件处理海量数据,代码清爽得很:
@GetMapping("/publicPool")
@PreAuthorize("hasAuthority('customer:public')")
public R<Page<CustomerVO>> getPublicPool(@RequestParam Map<String, Object> params) {
QueryWrapper<Customer> wrapper = new QueryWrapper<>();
wrapper.eq("status", 0).eq("is_deleted", 0);
if(params.containsKey("industry")){
wrapper.like("industry", params.get("industry"));
}
Page<Customer> page = customerService.page(
new PageQuery<Customer>().getPage(params), wrapper);
return R.ok(page.convert(customerConverter::entity2VO));
}
注意那个@PreAuthorize注解,SpringSecurity的权限控制直接做到方法级别。前端Vue这边用vxe-table组件展示数据,带筛选条件联动查询:
<vxe-table :data="tableData">
<vxe-column field="name" title="客户名称" />
<vxe-column field="industry" title="所属行业">
<template #filter>
<el-select v-model="filters.industry" @change="handleFilter">
<el-option label="IT" value="IT" />
<el-option label="制造" value="制造" />
</el-select>
</template>
</vxe-column>
</vxe-table>
审批流的设计有点门道。数据库里approval表用status字段区分审批状态(0待审/1通过/2驳回),关联business_type字段区分客户/合同等业务类型。前端用uni-app的swiper组件做审批卡片滑动:
// 小程序端审批列表
uni.request({
url: '/api/approval/list',
success: (res) => {
this.approvals = res.data.map(item => ({
...item,
statusClass: item.status === 0 ? 'pending' : item.status === 1 ? 'approved' : 'rejected'
}))
}
})
统计模块用ECharts搞了几个酷炫仪表盘。重点说下合同金额分析,后端用GROUP BY按月份聚合数据:
@Query(value = "SELECT DATE_FORMAT(sign_date,'%Y-%m') as month, SUM(amount) as total " +
"FROM contract WHERE deleted=0 GROUP BY month", nativeQuery = true)
List<Map<String, Object>> getContractAnalysis();
前端拿到数据后,折线图的option配置注意加了个dataZoom,防止月份太多挤成一团:
option = {
xAxis: {type: 'category', data: months},
yAxis: {type: 'value'},
series: [{data: amounts, type: 'line'}],
dataZoom: [{
type: 'slider',
start: 0,
end: 50
}]
}
系统管理里的部门树结构处理是个经典问题。后端递归查询用到了@JsonView控制返回字段,避免无限循环:
@Entity
public class Department {
@Id
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "parent_id")
@JsonView(Views.Base.class)
private Department parent;
@OneToMany(mappedBy = "parent")
@JsonView(Views.Detail.class)
private List<Department> children;
}
前端用el-tree组件展示,注意要转换平铺数据为树形结构。这里封装了个转换方法:
function listToTree(list) {
const map = {};
const roots = [];
list.forEach(item => {
map[item.id] = { ...item, children: [] };
if (!item.parentId) {
roots.push(map[item.id]);
} else {
map[item.parentId]?.children.push(map[item.id]);
}
});
return roots;
}
这套系统最骚的操作是UniApp小程序和PC端共用API接口。遇到个坑——小程序不支持PUT/DELETE方法,得在后端CORS配置里显式允许:
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("GET");
config.addAllowedMethod("POST");
config.addAllowedMethod("PUT"); // 小程序必须加这个
config.addAllowedMethod("DELETE");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
现在看这系统,技术选型算是中规中矩,但胜在功能闭环。特别是客户流转机制——公海客户超过7天未跟进自动释放,这业务逻辑用Spring的@Scheduled搞定:
@Scheduled(cron = "0 0 3 * * ?") // 每天凌晨3点执行
public void releaseInactiveCustomers() {
LocalDate deadline = LocalDate.now().minusDays(7);
customerRepo.releaseInactiveCustomers(deadline);
}
整套代码撸下来,最大的体会是业务复杂度总在奇怪的地方冒出来。比如合同审批要关联客户状态,回款得匹配合同阶段,这些业务规则在Service层里写成了连环判断。不过好在SpringBoot的事务管理够稳,不然数据一致性真能要了老命。



更多推荐




所有评论(0)