实践目的
本次实践主要任务为小组设计并开发“明光筑梦”志愿者服务平台的一个或几个子系统,在团队协作中体会软件项目系统从组建到研发再到开发实施的全过程。在团队组建中,需要我们协力组建一支能力分布均衡的队伍,为团队接下来的实验任务做好合理的分配和规划。在商模设计中,团队需要根据市场需求和商业目标,构建和优化整体商业模式,需要达到洞悉客户需求、创造和传递价值、设计和确定收益模式以及实现可持续发展的目的。在故事地图的绘制中,我们需要用故事绘制关键要素和创建用户路径跟踪,创造一个清晰的、可衡量的或不确定的环境来为后续的设计铺路。在系统搭建的过程中,团队需要达成为志愿者和留守儿童提供志愿者子系统、儿童子系统、捐助者子系统和管理者子系统中的任务,从而提升自身的前后端编码能力、团队协作能力和完善修补代码的能力,并用燃尽图达成预测趋势中的时间周期的目的,以及在集成测试中达到确保在多个独立的软件模块之间集成和交互时系统的正确性、一致性和可靠性。最后完成产品回顾和联合交付,目的是整合小组内容并为实践项目做总结以及完成整个实践的反思以提升个人技术素养。
有关内容
本次实践过程中需要多次用到代码测试工具,因此我首先预习了Java语言的单元测试框JUnit的相关知识,代码质量管理的开源平台SonarQube的相关内容以及基于Java开发的压力测试工具JMeter的有关知识,其次对于本项目采用的框架内容SpringBoot有了一定的了解[2],以便对本次实践开发项目的编程与测试内容夯实更为坚固的基础。
在开发过程中,我学习了maven工程的搭建,其次sql语句的编写,navicat的应用,mybatis-plus的应用,restful风格的开发,在前端中我学习了vue框架的使用。其次我还学习了开源的内存数据结构存储系统Reddis,开源容器化平台docker的应用,以及分布式版本控制系统git的应用。
故事地图
项目结构
该项目采用mvc架构
关键代码
工具类
该项目一个有四个工具类,用于处理全局
public class BaseContext {
private static ThreadLocal<Integer>threadLocal=new ThreadLocal<>();
public static void setCurrentId(Integer id){
threadLocal.set(id);
}
public static Integer getCurrentId(){
return threadLocal.get();
}
}
BaseContext工具类,基于ThreadLocal封装工具类 用于保存和获取当前登录用户的id
public class CustomException extends RuntimeException{
public CustomException(String message){
super(message);
}
}
CustomException工具类,为自定义异常
@Component
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
// log.info("出错了"+ex.getMessage());
// return R.error("出错了");
if(ex.getMessage().contains("Duplicate entry")){
String[] split = ex.getMessage().split(" ");
String msg = split[2] + "已存在";
return R.error(msg);
}
return R.error("未知错误");
}
@ExceptionHandler(CustomException.class)
public R<String> exceptionHandler(CustomException ex){
// log.info("出错了"+ex.getMessage());
// return R.error("出错了");
return R.error(ex.getMessage());
}
}
GlobalExceptionHandler全局异常处理类,用于处理全局异常
@Data
public class R<T> {
private Integer code;
private String msg;
private T data;
public static <T> R<T> success(T object) {
R<T> r = new R<T>();
r.data = object;
r.code = 666;
return r;
}
public static <T> R<T> error(String msg) {
R r = new R();
r.msg = msg;
r.code = 0;
return r;
}
}
R类,用于结果封装,将服务器返回的结果封装,并进行解读。
实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("allocation")
public class Allocation implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private Integer userId;
private Integer taskId;
private LocalDateTime time;
/*
0未完成
1完成
*/
@TableField("status")
private String status;
}
Allocation类,我们正片文章都以allocation类为例,来看本项目的controller,service,mapper层等等。 allocation为任务分配。
service,serviceimpl,mapper层
因为本项目使用了mybatis-plus框架,service,serviceimpl,mapper直接继承就好,所以就不展示了
controller层
@RestController
@RequestMapping(value = "/allocation")
public class AllocationController {
@Autowired
private ITaskService taskService;
@Autowired
private IAllocationService allocationService;
@Autowired
private IUserService userService;
@Autowired
private IDonateService donateService;
/*
查询可以分配的所有志愿者
*/
@GetMapping("/getAssignableVolunteers/{taskId}")
public R<List<User>>getAssignableVolunteers(@PathVariable("taskId") Integer taskId){
//查询所有志愿者
LambdaQueryWrapper<User>queryWrapper1=new LambdaQueryWrapper<>();
queryWrapper1.eq(User::getStatus,"1");
List<User> users = userService.list(queryWrapper1);
//查询taskId的任务分配情况
LambdaQueryWrapper<Allocation>queryWrapper2=new LambdaQueryWrapper<>();
queryWrapper2.eq(Allocation::getTaskId,taskId);
List<Allocation> allocations = allocationService.list(queryWrapper2);
//获取可分配的志愿者
List<User> assignableVolunteers = users.stream().filter(user -> !allocations.stream().anyMatch(allocation ->
allocation.getUserId().equals(user.getId()))).collect(Collectors.toList());
return R.success(assignableVolunteers);
}
/*
为任务分配志愿者
*/
@PostMapping("/assignVolunteer/{taskId}")
public R<String> assiginVolunteer(@PathVariable("taskId") Integer taskId,@RequestBody List<Integer> ids){
Task task = taskService.getById(taskId);
LocalDateTime now=LocalDateTime.now();
if(task.getEndTime().isBefore(now)||task.getStatus().equals("0")){
return R.error("任务已截止");
}
for (Integer id : ids) {
User user = userService.getById(id);
Allocation allocation=new Allocation();
allocation.setUserId(user.getId());
//allocation.setUserUserName(user.getUsername());
allocation.setTaskId(task.getId());
//allocation.setTaskName(task.getTaskName());
allocation.setStatus("0");
allocation.setTime(now);
allocationService.save(allocation);
}
return R.success("志愿者分配成功");
}
/*
查询志愿者当前任务数
*/
@GetMapping("/getTaskCount")
public R<Map<String,Integer>>getTaskCount(){
//查询所有的志愿者
LambdaQueryWrapper<User>queryWrapper1=new LambdaQueryWrapper<>();
queryWrapper1.eq(User::getStatus,"1");
List<User> userList = userService.list(queryWrapper1);
//查询每个志愿者当前任务数
Map<String,Integer>map=new TreeMap<>();
for (User user : userList) {
Integer userId = user.getId();
String username = user.getUsername();
LocalDateTime now = LocalDateTime.now();
LambdaQueryWrapper<Allocation>queryWrapper2=new LambdaQueryWrapper<>();
queryWrapper2.eq(Allocation::getUserId,userId)
.lt(Allocation::getTime,now);
Integer count = allocationService.count(queryWrapper2);
map.put(username,count);
}
return R.success(map);
}
/*
返回特定任务所有志愿者
*/
@GetMapping("/getAllocationList/{taskid}")
public R<List<User>>getAllocationList( @PathVariable int taskid){
LambdaQueryWrapper<User>queryWrapper1=new LambdaQueryWrapper<>();
queryWrapper1.eq(User::getStatus,"1");
List<User> users = userService.list(queryWrapper1);
//查询taskId的任务分配情况
LambdaQueryWrapper<Allocation>queryWrapper2=new LambdaQueryWrapper<>();
queryWrapper2.eq(Allocation::getTaskId,taskid);
List<Allocation> allocations = allocationService.list(queryWrapper2);
//获取该任务的志愿者
List<User> allocations1=new ArrayList<>();
for (Allocation allocation : allocations) {
Integer userId = allocation.getUserId();
for (User user : users) {
if(userId.equals(user.getId())){
allocations1.add(user);
}
}
}
return R.success(allocations1);
}
/*
返回特定任务所有志愿者
*/
/*
返回用户数userCount
当前任务数currentTaskCount
过期任务数expiredTaskCount
捐款总数donateSum
*/
@GetMapping("/getCount")
public R<Map<String,Integer>>getCount(){
Map<String,Integer>map=new HashMap<>();
LocalDateTime now = LocalDateTime.now();
//获取用户数
Integer userCount = userService.count();
map.put("userCount",userCount);
//获取当前任务数
LambdaQueryWrapper<Task>queryWrapper1=new LambdaQueryWrapper<>();
queryWrapper1.eq(Task::getStatus,"1").gt(Task::getEndTime,now);
int currentTaskCount = taskService.count(queryWrapper1);
int taskCount = taskService.count();
int expiredTaskCount = taskCount - currentTaskCount;
map.put("currentTaskCount",currentTaskCount);
map.put("expiredTaskCount",expiredTaskCount);
List<Donate> list = donateService.list();
int sum=0;
for (Donate donate : list) {
//
sum+=Integer.parseInt(donate.getMoney().toString());
}
map.put("donateSum",sum);
return R.success(map);
}
}
controller层中展示了一些基本的增删改查操作,在做查的时候没有做分页查询操作。我把分页查询做在了前端html页面中,所以后端没有实现分页查询功能。
过滤器
@Component
@WebFilter(filterName = "loginFilter", urlPatterns = "/*")
public class loginFilter implements Filter {
public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String requestURI = request.getRequestURI();
String[] urls = new String[]{
"/dist/**",
"/js/**",
"/",
"/utils/**",
"/index.html",
"/vendor/**",
"/pages/login.html",
"/user/login",
"/common/**",
};
boolean check = check(urls, requestURI);
if (check) {
filterChain.doFilter(request, response);
return;
}
/*
客户端
*/
if (request.getSession().getAttribute("userId") != null) {
Integer userId = (Integer)request.getSession().getAttribute("userId");
BaseContext.setCurrentId(userId);
filterChain.doFilter(request, response);
return;
}
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
return;
}
public boolean check(String[] urls, String url) {
for (String s : urls) {
boolean match = PATH_MATCHER.match(s, url);
if (match) {
return true;
}
}
return false;
}
}
为了保证该项目的安全,需要做一个登录过滤器,防止没登录直接使用该系统。
运行截图