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的事务管理够稳,不然数据一致性真能要了老命。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐