界面效果

用户管理界面

详情页面

编辑页面

删除功能

用户界面

查询余额

取出余额

存款

转账





后端包结构

bean包下代码

管理员类

package com.example.qqqundatabasedemo.bean.concreteuser;import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** @author bilibilidick* @version 2022 07* @date 2022/7/4 12:40*/@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("q_adminuser")
public class AdminUser {@TableField("user_name")private String userName;@TableField("admin_pwd")private String adminPwd;}

普通用户类

package com.example.qqqundatabasedemo.bean.concreteuser;import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.math.BigDecimal;/*** @author bilibilidick* @version 2022 07* @date 2022/7/4 12:40*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("q_bankuser")
public class BankUser {@TableField("card_id")@TableIdprivate String cardId;@TableField("card_pwd")private String cardPwd;@TableField("user_name")private String userName;private String phone;private BigDecimal account;}

dto下代码

package com.example.qqqundatabasedemo.bean.dto;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;import java.math.BigDecimal;/*** @author bilibilidick* @version 2022 07* @date 2022/7/5 23:20*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class QueryBalanceDTO {private String cardId;private String cardPwd;private String transferId;private BigDecimal money;}

config包下代码

跨域配置类

package com.example.qqqundatabasedemo.config;/*** @author bilibilidick* @version 2022 07* @date 2022/7/5 12:58*/import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;@Configuration
public class CorsConfig {// 当前跨域请求最大有效时长。这里默认1天private static final long MAX_AGE = 24 * 60 * 60;@Beanpublic CorsFilter corsFilter() {UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();CorsConfiguration corsConfiguration = new CorsConfiguration();corsConfiguration.addAllowedOrigin("*"); // 1 设置访问源地址corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法corsConfiguration.setMaxAge(MAX_AGE);source.registerCorsConfiguration("/**", corsConfiguration); // 4 对接口配置跨域设置return new CorsFilter(source);}}

MP分页配置

package com.example.qqqundatabasedemo.config;import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @author bilibilidick* @version 2022 07* @date 2022/7/5 12:24*/
@Configuration
public class MyBatisPlusConfig {@Beanpublic PaginationInterceptor paginationInterceptor() {return new PaginationInterceptor();}}

redis配置

package com.example.qqqundatabasedemo.config;import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @author bilibilidick* @version 2022 07* @date 2022/7/4 22:49*/
@Configuration
public class RedisConfig {@Beanpublic RedissonClient redissonClient() {Config config = new Config();config.useSingleServer().setAddress("redis://秘密").setPassword("秘密");return Redisson.create(config);}}

web配置

package com.example.qqqundatabasedemo.config;import com.example.qqqundatabasedemo.interceptor.AdminLoginInterceptor;
import com.example.qqqundatabasedemo.interceptor.RefreshTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.Resource;/*** @author bilibilidick* @version 2022 07* @date 2022/7/5 21:10*/
@Configuration
public class WebConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate));registry.addInterceptor(new AdminLoginInterceptor()).excludePathPatterns("/admin/login","/admin/logout");}
}

controller包下代码

管理员控制器

package com.example.qqqundatabasedemo.controller;import com.example.qqqundatabasedemo.bean.concreteuser.AdminUser;
import com.example.qqqundatabasedemo.bean.concreteuser.BankUser;
import com.example.qqqundatabasedemo.service.AdminService;
import com.example.qqqundatabasedemo.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;/*** @author bilibilidick* @version 2022 07* @date 2022/7/4 13:00*/@RestController
@RequestMapping("admin")
public class AdminController {@Autowiredprivate AdminService adminService;@GetMapping("handle")public Result getUserList(@RequestParam(defaultValue = "1") long page, @RequestParam(defaultValue = "5") long pageSize) {return adminService.queryUserByPage(page, pageSize);}@GetMapping("handle/{id}")public Result queryUser(@PathVariable("id") String id) {return adminService.queryUserById(id);}@DeleteMapping("handle/{id}")public Result removeUser(@PathVariable("id") String id) {return adminService.deleteUserById(id);}@PatchMapping("handle")public Result addUser(@RequestBody BankUser user) {return adminService.addUser(user);}@PostMapping("handle")public Result changeUser(@RequestBody BankUser user) {return adminService.updateUserById(user);}@PostMapping("login")public Result login(AdminUser user) {return adminService.adminLogin(user);}@PostMapping("logout")public Result logout(String token) {return adminService.logout(token);}}

普通用户控制器

package com.example.qqqundatabasedemo.controller;import com.example.qqqundatabasedemo.bean.dto.QueryBalanceDTO;
import com.example.qqqundatabasedemo.service.BankUserService;
import com.example.qqqundatabasedemo.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** @author bilibilidick* @version 2022 07* @date 2022/7/5 22:48*/
@RestController
@RequestMapping("user")
public class BankUserController {@Autowiredprivate BankUserService bankUserService;@PostMapping("handle")public Result queryBalance(QueryBalanceDTO queryBalanceDTO) {return bankUserService.queryBalance(queryBalanceDTO);}@PostMapping("withdraw")public Result withdrawBalance(QueryBalanceDTO queryBalanceDTO) {return bankUserService.withDrawBalance(queryBalanceDTO);}@PostMapping("deposit")public Result depositBalance(QueryBalanceDTO queryBalanceDTO) {return bankUserService.depositBalance(queryBalanceDTO);}@PostMapping("transfer")public Result transferBalance(QueryBalanceDTO queryBalanceDTO) {return bankUserService.transferBalance(queryBalanceDTO);}}

handler包下代码

package com.example.qqqundatabasedemo.handler;import com.example.qqqundatabasedemo.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;import javax.servlet.http.HttpServletRequest;/*** @author bilibilidick* @version 2022 07* @date 2022/7/4 22:46*/
@ControllerAdvice
@Slf4j
public class ControllerExceptionHandler {@ExceptionHandler(Exception.class)public Result exceptionHandle(HttpServletRequest request, Exception e) {log.error("Request URL :{} Exception:{}",request.getRequestURL(),e);return Result.error("Request URL :" + request.getRequestURL() + "Exception:" + e);}}

interceptor包下代码

刷新登陆状态拦截器

package com.example.qqqundatabasedemo.interceptor;import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.example.qqqundatabasedemo.bean.concreteuser.AdminUser;
import com.example.qqqundatabasedemo.utils.AdminUserHolder;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import java.util.Map;
import java.util.concurrent.TimeUnit;import static com.example.qqqundatabasedemo.utils.RedisConstants.LOGIN_TOKEN_KEY;/*** @author bilibilidick* @version 2022 07* @date 2022/7/5 20:57*/public class RefreshTokenInterceptor implements HandlerInterceptor {private final StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String token = request.getHeader("authentication");if (StrUtil.isBlank(token)) return true;Map<Object, Object> adminUserMap = stringRedisTemplate.opsForHash().entries(LOGIN_TOKEN_KEY + token);if (!MapUtil.isEmpty(adminUserMap)) {AdminUser adminUser = BeanUtil.fillBeanWithMap(adminUserMap, new AdminUser(), false);AdminUserHolder.saveAdminUser(adminUser);stringRedisTemplate.expire(LOGIN_TOKEN_KEY + token, 30L, TimeUnit.MINUTES);}return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {AdminUserHolder.removeAdminUser();}
}

验证登录状态拦截器

package com.example.qqqundatabasedemo.interceptor;import com.example.qqqundatabasedemo.utils.AdminUserHolder;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** @author bilibilidick* @version 2022 07* @date 2022/7/5 21:13*/public class AdminLoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (AdminUserHolder.getAdminUser() == null) {response.setStatus(401);response.sendRedirect("https://localhost/admin/login");return false;} else return true;}
}

mapper包下代码

管理员mapper接口

package com.example.qqqundatabasedemo.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.qqqundatabasedemo.bean.concreteuser.AdminUser;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;@Repository
@Mapper
public interface AdminUserMapper extends BaseMapper<AdminUser> {
}

普通用户mapper接口

package com.example.qqqundatabasedemo.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.qqqundatabasedemo.bean.concreteuser.BankUser;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;/*** @author bilibilidick* @version 2022 07* @date 2022/7/4 12:54*/@Repository
@Mapper
public interface BankUserMapper extends BaseMapper<BankUser> {
}

service包下代码

管理员服务层接口

package com.example.qqqundatabasedemo.service;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.qqqundatabasedemo.bean.concreteuser.AdminUser;
import com.example.qqqundatabasedemo.bean.concreteuser.BankUser;
import com.example.qqqundatabasedemo.mapper.AdminUserMapper;
import com.example.qqqundatabasedemo.vo.Result;public interface AdminService{Result queryUserByPage(long page, long pageSize);Result queryUserById(String id);Result deleteUserById(String id);Result addUser(BankUser user);int deleteById(String id);Result updateUserById(BankUser user);Result adminLogin(AdminUser user);Result logout(String token);
}

管理员服务层接口实现类代码

package com.example.qqqundatabasedemo.service.impl;import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.qqqundatabasedemo.bean.concreteuser.AdminUser;
import com.example.qqqundatabasedemo.bean.concreteuser.BankUser;
import com.example.qqqundatabasedemo.mapper.AdminUserMapper;
import com.example.qqqundatabasedemo.mapper.BankUserMapper;
import com.example.qqqundatabasedemo.service.AdminService;
import com.example.qqqundatabasedemo.utils.RedisUtil;
import com.example.qqqundatabasedemo.vo.Result;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;import static com.example.qqqundatabasedemo.utils.RedisConstants.*;/*** @author bilibilidick* @version 2022 07* @date 2022/7/5 12:28*/
@Service
public class AdminServiceImpl extends ServiceImpl<AdminUserMapper, AdminUser> implements AdminService  {private final Random random = new Random();@Autowiredprivate BankUserMapper bankUserMapper;@Autowiredprivate AdminUserMapper adminUserMapper;@Autowiredprivate RedisUtil redisUtil;@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result queryUserByPage(long page, long pageSize) {Page<BankUser> pObj = new Page<>(page, pageSize);IPage<BankUser> userIPage = bankUserMapper.selectPage(pObj, null);return Result.success(userIPage);}@Overridepublic Result queryUserById(String id) {BankUser bankUser = queryUserMutex(id);return Result.success(bankUser);}@Overridepublic Result deleteUserById(String id) {AdminService proxy = (AdminService) AopContext.currentProxy();String msg = proxy.deleteById(id) > 0 ? "删除成功" : "删除失败";return "删除成功".equals(msg) ? Result.success(msg) : Result.error(msg);}@Overridepublic Result addUser(BankUser user) {String msg = addByUser(user) > 0 ? "添加成功" : "添加失败";return "添加成功".equals(msg) ? Result.success(msg) : Result.error(msg);}public int addByUser(BankUser user) {long rawId = random.nextLong();long cardId = rawId > 0 ? rawId : -1 * rawId;user.setCardId(Long.toString(cardId));return bankUserMapper.insert(user);}@Transactionalpublic int deleteById(String id) {int result = bankUserMapper.deleteById(id);if (result > 0) {stringRedisTemplate.delete(CACHE_USER_KEY + id);}return result;}@Transactional@Overridepublic Result updateUserById(BankUser user) {int result = bankUserMapper.updateById(user);if (result > 0) {stringRedisTemplate.delete(CACHE_USER_KEY + user.getCardId());}return result > 0 ? Result.success("更新成功") : Result.error("更新失败");}@Overridepublic Result adminLogin(AdminUser user) {QueryWrapper<AdminUser> wrapper = new QueryWrapper<>();wrapper.eq("user_name", user.getUserName());wrapper.eq("admin_pwd",user.getAdminPwd());AdminUser adminUser = adminUserMapper.selectOne(wrapper);if (adminUser != null) {String token = UUID.randomUUID().toString(true);Map<String, Object> adminUserMap = BeanUtil.beanToMap(adminUser, new HashMap<>(),new CopyOptions().setIgnoreNullValue(true).setFieldValueEditor((k , v) -> v.toString()));stringRedisTemplate.opsForHash().putAll(LOGIN_TOKEN_KEY + token, adminUserMap);stringRedisTemplate.expire(LOGIN_TOKEN_KEY + token, 30L, TimeUnit.MINUTES);return Result.success(token);}return Result.error("用户名或密码错误");}@Overridepublic Result logout(String token) {Boolean result = stringRedisTemplate.delete(LOGIN_TOKEN_KEY + token);return Boolean.TRUE.equals(result) ? Result.success("已在服务器端注销用户信息") : Result.error("服务器端注销失败或没有要注销的用户,将在半小时内自动回收账号登录信息");}public BankUser queryById(String id) {return bankUserMapper.selectById(id);}public BankUser queryUserMutex(String id) {return redisUtil.getWithMutex(BankUser.class, CACHE_USER_KEY, LOCK_USER_KEY, id, CACHE_USER_TTL, CACHE_LOCK_TTL, TimeUnit.MINUTES, this::queryById);}}

用户服务层接口

package com.example.qqqundatabasedemo.service;import com.example.qqqundatabasedemo.bean.dto.QueryBalanceDTO;
import com.example.qqqundatabasedemo.vo.Result;public interface BankUserService {Result queryBalance(QueryBalanceDTO dto);Result withDrawBalance(QueryBalanceDTO queryBalanceDTO);Result depositBalance(QueryBalanceDTO queryBalanceDTO);Result transferBalance(QueryBalanceDTO queryBalanceDTO);
}

用户服务层接口实现类

package com.example.qqqundatabasedemo.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.qqqundatabasedemo.bean.concreteuser.BankUser;
import com.example.qqqundatabasedemo.bean.dto.QueryBalanceDTO;
import com.example.qqqundatabasedemo.mapper.BankUserMapper;
import com.example.qqqundatabasedemo.service.BankUserService;
import com.example.qqqundatabasedemo.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.math.BigDecimal;/*** @author bilibilidick* @version 2022 07* @date 2022/7/5 23:21*/
@Service
public class BankUserServiceImpl implements BankUserService {@Autowiredprivate BankUserMapper bankUserMapper;@Overridepublic Result queryBalance(QueryBalanceDTO dto) {QueryWrapper<BankUser> wrapper = new QueryWrapper<>();wrapper.eq("card_id", dto.getCardId());wrapper.eq("card_pwd", dto.getCardPwd());BankUser bankUser = bankUserMapper.selectOne(wrapper);if (bankUser != null) return Result.success(bankUser.getAccount());else return Result.error("银行卡号或密码错误");}@Overridepublic Result withDrawBalance(QueryBalanceDTO dto) {QueryWrapper<BankUser> wrapper = new QueryWrapper<>();wrapper.eq("card_id", dto.getCardId());wrapper.eq("card_pwd", dto.getCardPwd());BankUser bankUser = bankUserMapper.selectOne(wrapper);if (bankUser != null) {if (bankUser.getAccount().subtract(dto.getMoney()).compareTo(BigDecimal.ZERO) < 0) {return Result.error("卡内余额不足,无法取款");} else {BigDecimal result = bankUser.getAccount().subtract(dto.getMoney());bankUser.setAccount(result);bankUserMapper.updateById(bankUser);}return Result.success(bankUser.getAccount());}else return Result.error("银行卡号或密码错误");}@Overridepublic Result depositBalance(QueryBalanceDTO dto) {QueryWrapper<BankUser> wrapper = new QueryWrapper<>();wrapper.eq("card_id", dto.getCardId());wrapper.eq("card_pwd", dto.getCardPwd());BankUser bankUser = bankUserMapper.selectOne(wrapper);if (bankUser != null) {if (dto.getMoney().compareTo(BigDecimal.ZERO) < 0) {return Result.error("存款金额需大于零");}bankUser.setAccount(bankUser.getAccount().add(dto.getMoney()));bankUserMapper.updateById(bankUser);return Result.success(bankUser.getAccount());}else return Result.error("银行卡号或密码错误");}@Transactional@Overridepublic Result transferBalance(QueryBalanceDTO dto) {QueryWrapper<BankUser> wrapper = new QueryWrapper<>();wrapper.eq("card_id", dto.getCardId());wrapper.eq("card_pwd", dto.getCardPwd());BankUser bankUser = bankUserMapper.selectOne(wrapper);if (bankUser != null) {if (bankUser.getAccount().subtract(dto.getMoney()).compareTo(BigDecimal.ZERO) < 0) {return Result.error("卡内余额不足,无法转账");} else {BankUser target = bankUserMapper.selectById(dto.getTransferId());if (target == null) {return Result.error("转账用户不存在");} else {StringBuilder stringBuilder = new StringBuilder("转帐前本人账户与目标账户的余额分别为" + bankUser.getAccount() + "      " + target.getAccount());target.setAccount(target.getAccount().add(dto.getMoney()));bankUserMapper.updateById(target);bankUser.setAccount(bankUser.getAccount().subtract(dto.getMoney()));bankUserMapper.updateById(bankUser);stringBuilder.append("转账后本人账户与目标账户的余额分别为").append(bankUser.getAccount()).append("      ").append(target.getAccount());return Result.success(stringBuilder);}}}else return Result.error("银行卡号或密码错误");}
}

utils包下代码

线程存储管理员登录信息类

package com.example.qqqundatabasedemo.utils;import com.example.qqqundatabasedemo.bean.concreteuser.AdminUser;/*** @author bilibilidick* @version 2022 07* @date 2022/7/5 21:05*/public class AdminUserHolder {private static final ThreadLocal<AdminUser> tl = new ThreadLocal<>();public static void saveAdminUser(AdminUser adminUser) {tl.set(adminUser);}public static AdminUser getAdminUser() {return tl.get();}public static void removeAdminUser() {tl.remove();}}

常量池

package com.example.qqqundatabasedemo.utils;/*** @author bilibilidick* @version 2022 07* @date 2022/7/5 13:32*/public class RedisConstants {public static final String CACHE_USER_KEY = "cache:user:";public static final String LOCK_USER_KEY = "lock:user:";public static final String LOGIN_TOKEN_KEY = "login:admin:token:";public static final long CACHE_LOCK_TTL = 3L;public static final long CACHE_USER_TTL = 30L;}

redis工具类

package com.example.qqqundatabasedemo.utils;import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;import static com.example.qqqundatabasedemo.utils.RedisConstants.CACHE_LOCK_TTL;/*** @author bilibilidick* @version 2022 07* @date 2022/7/5 18:58*/
@Component
public class RedisUtil {@Resourceprivate StringRedisTemplate stringRedisTemplate;public <E,ID> E getWithMutex(Class<E> clazz, String keyPrefix, String lockKeyPrefix, ID id, long timeout, long emptyTimeout, TimeUnit unit, Function<ID, E> function) {E value = null;try {// 从redis中查询相应ID的商品信息String shopJson = stringRedisTemplate.opsForValue().get(keyPrefix + id);// 查到则直接返回if (StrUtil.isNotBlank(shopJson)){return JSONUtil.toBean(shopJson, clazz);}// 未查到则查询MYSQL数据库,查询到则将数据存储到redis中并返回,查不到则返回404// 为防止缓存穿透,如果未查到数据则缓存空字符串放入redis中if ("".equals(shopJson)) {return null;}// 如果缓存中不存在这个数据或者缓存已过期,则互斥的进行缓存重建if (tryLock(lockKeyPrefix, id, "1")) {// 从redis中再次查询相应ID的商品信息shopJson = stringRedisTemplate.opsForValue().get(keyPrefix + id);// 查到则直接返回if (StrUtil.isNotBlank(shopJson)){return JSONUtil.toBean(shopJson, clazz);}value = function.apply(id);if (value == null) {// XXX stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "", CACHE_EMPTY_TTL, TimeUnit.MINUTES);set(keyPrefix + id, "",  emptyTimeout, unit);return null;} else {// XXX stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);set(keyPrefix + id, value,  timeout, unit);}} else {Thread.sleep(20);return getWithMutex(clazz, keyPrefix, lockKeyPrefix, id, timeout, emptyTimeout, unit, function);}} catch (InterruptedException e) {e.printStackTrace();} finally {unLock(lockKeyPrefix, id);}return value;}public <E> void set(String key, E obj, long timeout, TimeUnit unit) {stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(obj), timeout, unit);}public<E> boolean tryLock(String keyPrefix, E id, String value) {Boolean absent = stringRedisTemplate.opsForValue().setIfAbsent(keyPrefix + id, value, CACHE_LOCK_TTL, TimeUnit.SECONDS);return BooleanUtil.isTrue(absent);}public<E> void unLock(String keyPrefix, E id) {stringRedisTemplate.delete(keyPrefix + id);}}

vo包下代码

package com.example.qqqundatabasedemo.vo;import lombok.Data;/*** @author bilibilidick* @version 2022 07* @date 2022/7/4 20:17*/
@Data
public class Result {private int code;private Object msg;private Result(int code, Object msg) {this.code = code;this.msg = msg;}public static Result success(Object msg) {return new Result(200, msg);}public static Result error(Object msg) {return new Result(500, msg);}
}




前端包结构,使用Vue2书写前端

main.js代码

import Vue from 'vue'
import App from './App.vue'
import VueRouter from "vue-router";
import ElementUI from 'element-ui';
import router from "@/route";
import 'element-ui/lib/theme-chalk/index.css';
import request from "@/network/request";
Vue.config.productionTip = false
Vue.use(ElementUI)
Vue.use(VueRouter)
Vue.prototype.$request = request
new Vue({render: h => h(App),router
}).$mount('#app')

路由配置

import VueRouter from "vue-router";
import Home from "@/page/Home";
import Admin from "@/page/Admin";
import Login from "@/page/Login";
import ElementUI from "element-ui";
import UserMenu from "@/page/UserMenu";
import UserQuery from "@/page/UserQuery";
import UserTransfer from "@/page/UserTransfer";const router = new VueRouter({routes: [{path: '/home',name: 'home',component: Home,meta: {title: '主页'}},{path: '/admin',name: 'admin',component: Admin,meta: {title: '后台管理'}},{path: '/login',name: 'login',component: Login,meta: {title: '登录'}},{path: '/user',name: 'user',component: UserMenu,meta: {title: '用户操作菜单'},children: [{path: 'userquery',name: 'userquery',component: UserQuery,meta: {title: '查询余额'}},{path: 'usertransfer',name: 'usertransfer',component: UserTransfer,meta: {title: '用户转账'}}]}]
})router.beforeEach((to, from, next) => {let token = sessionStorage.getItem('usertoken')if (token == null) {if (to.name === 'login' || to.name === 'home') {next()return} else {ElementUI.Notification({title: 'Code: 403',type: "error",message: '请先登录',showClose: true})next("/login")return}}if (to.name === 'login') {next('/home')} else next()})//全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to, from) => {document.title = to.meta.title || '天气识别系统'
})export default router

请求头与响应体的拦截器配置

import axios from "axios";
import ElementUI from "element-ui";
const instance = axios.create({baseURL: 'http://localhost:8080/'
})
instance.interceptors.request.use(config => {let token = window.sessionStorage.getItem('usertoken')if (token == null) {//} else {config['headers']['authentication'] = token//data.data.normal_login_token为发送Ajax获取到的token信息}/*if (typeof token != "undefined" && token != null && token !== 'undefined'){config['headers'].token = JSON.parse(token)}*/return config;},err => {//})instance.interceptors.response.use(res => {if (res.data.code === 200) {console.log(res.data)ElementUI.Notification({title: 'Code: ' + res.data.code,type: "success",message: res.data.msg,showClose: true})} else if (res.data.code === 401) {window.sessionStorage.removeItem('usertoken')ElementUI.Notification({title: 'Code: ' + res.data.code,type: "error",message: res.data.msg,showClose: true})} else {ElementUI.Notification({title: 'Code: ' + res.data.code,type: "error",message: res.data.msg,showClose: true})}return res
})export default instance

UserHeader

<template><el-menu:default-active="activeIndex"class="el-menu-demo"mode="horizontal"@select="handleSelect"background-color="#545c64"text-color="#fff"active-text-color="#ffd04b"><el-menu-item index="1" @click.native="$router.push('userquery')">查询与存取款业务</el-menu-item><el-menu-item index="2" @click.native="$router.push('usertransfer')">转账业务</el-menu-item><el-menu-item index="3" @click.native="$router.push('/admin')">后台管理</el-menu-item></el-menu>
</template><script>
export default {name: "UserHeader",data() {return {activeIndex: '1',};},methods: {handleSelect(key, keyPath) {console.log(key, keyPath);}}
}
</script><style scoped></style>

Admin

<template><div><el-dropdown split-button type="primary" @click="handleLogout">管理员身份注销<el-dropdown-menu slot="dropdown"><el-dropdown-item @click.native="$router.push({name:'userquery'})">用户页面</el-dropdown-item></el-dropdown-menu></el-dropdown><el-table:data="tableData"borderstyle="width: 100%"><el-table-columnfixedprop="cardId"label="银行卡号"width="auto"></el-table-column><el-table-columnprop="userName"label="用户名"width="auto"></el-table-column><el-table-columnprop="account"label="账户余额"width="auto"></el-table-column><el-table-columnfixed="right"label="操作"width="auto"><template slot-scope="scope"><el-button type="text" @click=handleQuery(scope.row) size="small">详情</el-button><el-button @click="handleClick(scope.row)" type="text" size="small">编辑</el-button><el-button @click="handleDelete(scope.row)" type="text" size="small">删除</el-button></template></el-table-column></el-table><div class="block" style="background-color: white"><el-pagination@current-change="handleCurrentChange":current-page.sync="currentPage":page-size="pageSize"layout="prev, pager, next, jumper":total="dataTotal"></el-pagination></div><el-row><el-button @click="handleAdd" type="success">新增</el-button></el-row><!-- Form -->
<!--    <el-button type="text" @click="dialogFormVisible = true">打开嵌套表单的 Dialog</el-button>--><el-dialog title="用户信息" :visible.sync="dialogFormVisible"><el-form :model="form"><el-form-item label="用户Id(自动生成)" :label-width="formLabelWidth"><el-input readonly v-model="form.cardId" autocomplete="off"></el-input></el-form-item><el-form-item label="用户名" :label-width="formLabelWidth"><el-input v-model="form.userName" autocomplete="off"></el-input></el-form-item><el-form-item label="用户银行卡密码" :label-width="formLabelWidth"><el-input v-model="form.cardPwd" autocomplete="off"></el-input></el-form-item><el-form-item label="用户电话" :label-width="formLabelWidth"><el-input v-model="form.phone" autocomplete="off"></el-input></el-form-item><el-form-item label="用户余额" :label-width="formLabelWidth"><el-input v-model="form.account" autocomplete="off"></el-input></el-form-item></el-form><div slot="footer" class="dialog-footer"><el-button @click="dialogFormVisible = false;form = {}">取 消</el-button><el-button type="primary" @click="handleUpdate">确 定</el-button></div></el-dialog><el-dialog title="详细信息" :visible.sync="dialogTableVisible"><el-table :data="gridData"><el-table-column property="cardId" label="银行卡号" width="150"></el-table-column><el-table-column property="userName" label="姓名" width="200"></el-table-column><el-table-column property="cardPwd" label="银行卡密码"></el-table-column><el-table-column property="phone" label="电话号码"></el-table-column><el-table-column property="account" label="余额"></el-table-column></el-table></el-dialog></div>
</template><script>
export default {name: "Admin",data() {return {activeIndex: '1',activeIndex2: '1',currentPage: 1,pageSize: 100,dataTotal: 1000,tableData: [],gridData: [],dialogFormVisible: false,dialogTableVisible: false,form: {},formLabelWidth: '120px'};},methods: {handleSelect(key, keyPath) {console.log(key, keyPath);},handleClick(row) {console.log(row);this.form = rowthis.dialogFormVisible = true},handleUpdate() {if (this.form.cardId !== undefined) {console.log("更新操作")this.dialogFormVisible = falseconsole.log(JSON.stringify(this.form))this.$request.post("admin/handle", JSON.stringify(this.form),{ headers: { "Content-Type": "application/json;charset=utf-8" } }).then(res => {//this.init(this.currentPage)})} else {console.log("新增操作")this.dialogFormVisible = falseconsole.log(JSON.stringify(this.form))this.$request.patch("admin/handle", JSON.stringify(this.form),{ headers: { "Content-Type": "application/json;charset=utf-8" } }).then(res => {//this.init(this.currentPage)})}this.form = {}},handleAdd() {this.dialogFormVisible = true},handleDelete(row) {this.$request.delete(`admin/handle/${row.cardId}`).then(res => {this.init(this.currentPage)})},handleQuery(row) {this.dialogTableVisible = truethis.gridData = []this.$request.get(`admin/handle/${row.cardId}`).then(res => {console.log(res.data.msg)this.gridData.push(res.data.msg)})},handleCurrentChange(val) {this.init(val)},handleLogout() {let token = window.sessionStorage.getItem("usertoken")window.sessionStorage.removeItem("usertoken")this.$notify.success({title: 'Code: 200',message: '前端注销成功',showClose: true});let params = new URLSearchParams()params.append("token", token)this.$request.post("admin/logout", params).then(res => {console.log(res.data.msg)})this.$router.push("/login")},init(params) {this.$request.get(`/admin/handle?page=${params}`).then(res => {if (res.data.code === 200) {this.currentPage = res.data.msg.currentthis.tableData = res.data.msg.recordsthis.pageSize = res.data.msg.sizethis.dataTotal = res.data.msg.totalif (this.tableData.length === 0) {this.init(params - 1)}}})}},mounted() {this.init(this.currentPage)}
}
</script><style scoped></style>

Home

<template>
<div>我是主页</div>
</template><script>
export default {name: "Home"
}
</script><style scoped></style>

Login

<template><div class="login_box"><div class="login_content"><el-form:model="ruleForm"status-icon:rules="rules"ref="ruleForm"label-width="65px"class="loginContainer"><h1 style="text-align: center">用户登录</h1><el-form-item label="用户名" prop="username"><el-input type="text" v-model="ruleForm.username" autocomplete="off"></el-input></el-form-item><el-form-item label="密码" prop="password"><el-input type="password" v-model="ruleForm.password" autocomplete="off"></el-input></el-form-item><el-form-item><el-button type="primary" @click="submitForm('ruleForm')">登录</el-button><el-button type="primary" @click.native="toRegister">跳转到注册页面</el-button><el-button style="margin-top: 20px;margin-left: 80px" @click="goHome" class="test">跳转到首页</el-button><el-button style="margin-top: 20px;margin-left: 80px" @click="resetForm('ruleForm')" class="test">重置</el-button></el-form-item></el-form></div></div>
</template><script>
export default {name: 'Login',data() {return {ruleForm: {username: "",password: ""},rules: {username: [{ required: true, message: "请输入用户名", trigger: "blur" },{ min: 5, max: 20, message: "长度在 5 到 20 个字符", trigger: "blur" }],password: [{ required: true, message: "请输入密码", trigger: "blur" },{ min: 6, max: 16, message: "长度在 6 到 16 个字符", trigger: "blur" }]}};},methods: {submitForm(formName) {this.$refs[formName].validate((valid) => {if (valid) {let params = new URLSearchParams();params.append('userName', this.ruleForm.username); //你要传给后台的参数值 key/valueparams.append('adminPwd', this.ruleForm.password); //你要传给后台的参数值 key/valuethis.$request.post('admin/login/',params).then(res => {if (res.data.code === 200) {window.sessionStorage.setItem('usertoken',res.data.msg)this.$router.push({name: 'admin'})}}).catch(err => {this.$notify.error({title: 'Code: 500',message: err.message + '\n请启动后端服务器或检查网络设置!',showClose: true});})} else {console.log('error submit!!');return false;}});},resetForm(formName) {this.$refs[formName].resetFields();},toRegister() {this.$router.push({name: 'zhuce'})},goHome() {this.$router.push({name: 'zhuyaobufen'})}}
}
</script><style scoped>
.loginContainer {border-radius: 15px;background-clip: padding-box;margin: 188px auto;width: 400px;padding: 15px 34px 14px 35px;background: #fff;border: 5px solid #eaeaea;box-shadow: 0 0 25px #cac6c6;
}
</style>

UserMenu

<template><div><UserHeader/><router-view/></div>
</template><script>
import UserHeader from "@/components/UserHeader";export default {name: "UserMenu",components: {UserHeader}
}
</script><style scoped></style>

UserQuery

<template><div><el-form style="background-color: white" :inline="true" :model="formInline" class="demo-form-inline"><el-form-item label="银行卡号码"><el-input v-model="formInline.cardId" placeholder="银行卡号"></el-input></el-form-item><el-form-item label="银行卡密码"><el-input v-model="formInline.cardPwd" placeholder="卡号密码"></el-input></el-form-item><el-form-item><el-button type="primary" @click="checkBalance">查询余额</el-button><el-button type="primary" @click="toWithDraw">取款</el-button><el-button type="primary" @click="toDeposit">存款</el-button><el-button type="primary" @click="toTransfer">转账</el-button></el-form-item></el-form><el-dialog title="用户存款界面" :visible.sync="dialogDepositFormVisible"><el-form :model="form"><el-form-item  label="银行卡号" :label-width="formLabelWidth"><el-input readonly v-model="formInline.cardId" autocomplete="off"></el-input></el-form-item><el-form-item  label="卡号密码" :label-width="formLabelWidth"><el-input readonly v-model="formInline.cardPwd" autocomplete="off"></el-input></el-form-item><el-form-item label="金额" :label-width="formLabelWidth"><el-input v-model="form.money" autocomplete="off"></el-input></el-form-item></el-form><div slot="footer" class="dialog-footer"><el-button @click="dialogDepositFormVisible = false">取 消</el-button><el-button type="primary" @click="depositHandle">确 定</el-button></div></el-dialog><el-dialog title="用户取款界面" :visible.sync="dialogWithDrawFormVisible"><el-form :model="form"><el-form-item  label="银行卡号" :label-width="formLabelWidth"><el-input readonly v-model="formInline.cardId" autocomplete="off"></el-input></el-form-item><el-form-item  label="卡号密码" :label-width="formLabelWidth"><el-input readonly v-model="formInline.cardPwd" autocomplete="off"></el-input></el-form-item><el-form-item label="金额" :label-width="formLabelWidth"><el-input v-model="form.money" autocomplete="off"></el-input></el-form-item></el-form><div slot="footer" class="dialog-footer"><el-button @click="dialogWithDrawFormVisible = false">取 消</el-button><el-button type="primary" @click="withDrawHandle">确 定</el-button></div></el-dialog><el-dialog title="用户转账界面" :visible.sync="dialogTransferFormVisible"><el-form :model="form"><el-form-item  label="银行卡号" :label-width="formLabelWidth"><el-input readonly v-model="formInline.cardId" autocomplete="off"></el-input></el-form-item><el-form-item  label="卡号密码" :label-width="formLabelWidth"><el-input readonly v-model="formInline.cardPwd" autocomplete="off"></el-input></el-form-item><el-form-item label="转账卡号" :label-width="formLabelWidth"><el-input v-model="form.transferId" autocomplete="off"></el-input></el-form-item><el-form-item label="转账金额" :label-width="formLabelWidth"><el-input v-model="form.money" autocomplete="off"></el-input></el-form-item></el-form><div slot="footer" class="dialog-footer"><el-button @click="dialogTransferFormVisible = false">取 消</el-button><el-button type="primary" @click="transferHandle">确 定</el-button></div></el-dialog></div>
</template><script>
export default {name: "UserQuery",data() {return {formInline: {cardId: '',cardPwd: ''},dialogDepositFormVisible: false,dialogWithDrawFormVisible: false,dialogTransferFormVisible: false,form: {money: 0,transferId: ''},formLabelWidth: '120px'}},methods: {toWithDraw() {this.dialogWithDrawFormVisible = true},withDrawHandle() {let params = new URLSearchParams();params.append("cardId", this.formInline.cardId)params.append("cardPwd", this.formInline.cardPwd)params.append("money", this.form.money)this.$request.post("user/withdraw", params).then(res => {if (res.data.code === 200) {this.$notify.success({title: 'Code: 200',message: '取款成功,卡内余额为' + res.data.msg,showClose: true});}})this.dialogWithDrawFormVisible = false},toDeposit() {this.dialogDepositFormVisible = true},depositHandle() {let params = new URLSearchParams();params.append("cardId", this.formInline.cardId)params.append("cardPwd", this.formInline.cardPwd)params.append("money", this.form.money)this.$request.post("user/deposit", params).then(res => {if (res.data.code === 200) {this.$notify.success({title: 'Code: 200',message: '存款成功,卡内余额为' + res.data.msg,showClose: true});}})this.dialogDepositFormVisible = false},toTransfer() {this.dialogTransferFormVisible = true},transferHandle() {let params = new URLSearchParams();params.append("cardId", this.formInline.cardId)params.append("cardPwd", this.formInline.cardPwd)params.append("money", this.form.money)params.append("transferId", this.form.transferId)this.$request.post("user/transfer", params).then(res => {if (res.data.code === 200) {this.$notify.success({title: 'Code: 200',message: '转账成功,卡内余额为' + res.data.msg,showClose: true});}})this.dialogTransferFormVisible = false},checkBalance() {let params = new URLSearchParams();params.append("cardId", this.formInline.cardId)params.append("cardPwd", this.formInline.cardPwd)this.$request.post("user/handle", params).then(res => {if (res.data.code === 200) {console.log(res.data.msg)this.$notify.success({title: 'Code: 200',message: '卡内余额为' + res.data.msg,showClose: true});}})}}
}
</script><style scoped></style>

UserTransfer

<template>
<div><el-steps :active="0" simple><el-step title="填写银行卡号" icon="el-icon-edit"></el-step><el-step title="填写银行卡密码" icon="el-icon-upload"></el-step><el-step title="填写转账人银行卡号" icon="el-icon-picture"></el-step><el-step title="填写转账金额" icon="el-icon-picture"></el-step><el-step title="确认" icon="el-icon-picture"></el-step></el-steps>
</div>
</template><script>
export default {name: "UserTransfer"
}
</script><style scoped></style>

十分钟写一个基于springboot+vue+redis+mysql的银行转账与用户后台管理系统,redis实现用户登录与缓存相关推荐

  1. 基于springboot+vue前后端分离的学生在线考试管理系统

    一.基于springboot+vue前后端分离的学生在线考试管理系统 本系统通过教师用户创建班级编写试卷信息然后发布到班级.学生用户进入班级,在线作答,考试结果数据通过网络回收,系统自动进行判分,生成 ...

  2. 推荐一个基于Springboot+Vue的开源博客系统

    简介 这是一个基于Springboot2.x,vue2.x的前后端分离的开源博客系统,提供 前端界面+管理界面+后台服务 的整套系统源码.响应式设计,手机.平板.PC,都有良好的视觉效果! 你可以拿它 ...

  3. 一个基于 SpringBoot+Vue 仿饿了么外卖系统(后台+移动端),可二次开发 。

    来源:https://mp.weixin.qq.com/s/i2siD8bJ334z9tgsVXEx1Q 该项目是一款仿饿了么外卖平台系统,参考了一些现有其他开源外卖项目,在此基础之上,做了优化处理, ...

  4. Springboot Vue前后端分离实现基础的后台管理系统

    最近在利用空闲的时间学习springboot+Vue前后端分离相关知识,然后动手写了个后台管理系统,实现登录验证.学生模块.英雄联盟模块.数据可视化(暂未开发,准备使用echarts进行):这边先以英 ...

  5. 基于springboot+vue(thymeleaf)+mysql下的自创音乐网站平台--CrushMusic(开发日志十四)--audio控件重写音乐播放

    本次花了很大精力去完成了播放界面,虽然歌词同步这里没完成,但后续还是可以完善的,这次我重写了audio控件,让audio是自己想要的样式,先看成果图.  这个界面参考的是酷狗音乐网页版的布局,感觉自己 ...

  6. 教你十分钟写一个软件防火墙

    首先在记事本中写入想禁止的清单 注: 只输入程序名,不输入文件后缀 ,一个程序一行 这里以火狐为例 代码如下: $process_name = type "black.txt" # ...

  7. 基于springboot+vue(thymeleaf)+mysql下的自创音乐网站平台--CrushMusic(开发日志七)

    中间隔了有一段时间,因为去学了一些其他东西,加上还有课程,今天专门用了大半天来写前台界面与登录注册的UI设计,网上的模板我都不太想用,我就用框架来自己搭建前台,我参考了三大音乐网站的首页设计,基本都是 ...

  8. java基于Springboot+vue 的在线药品销售商城购药管理系统 elementui

    系统管理也都将通过计算机进行整体智能化操作,对于药品管理系统所牵扯的管理及数据保存都是非常多的,例如管理员:首页.个人中心.用户管理.员工管理.药品类别管理.药品信息管理.药品入库管理.药品出库管理. ...

  9. 基于SpringBoot+Vue+Java+Mysql 的简历招聘系统【源码】

    文章目录 1.效果演示 2. 前言介绍 3.主要技术 4 **系统设计** 4.1 系统体系结构 4.2开发流程设计 4.3 数据库设计原则 4.4 数据表 5 **系统详细设计** 5.1管理员功能 ...

最新文章

  1. 文件翻译002片:Process Monitor帮助文档(Part 2)
  2. 记录自己的学习和经验
  3. 扬言要干掉 RESTful API 的 GraphQL 是什么鬼?
  4. linux驱动调试--oops信息
  5. 使用Select.HtmlToPdf 把html内容生成pdf文件
  6. docker ssh
  7. hadoop--常见错误及解决方法
  8. java复选框如何显示在文本雨_java – 在复选框的itemStatechanged事件上显示文本字段...
  9. Net学习日记_ADO.Net_2_练习(treeView)
  10. 员工为什么缺乏执行力?
  11. spring cloud gateway Unhandled failure: Only one connection receive subscriber allowed.
  12. mt4代理服务器存放文件,mt4如何保存设置好的指标?
  13. 财务模块的一些基础概念
  14. 公司与公司保密协议范本
  15. 计算机如何接6块显卡,双显卡怎么切换到独立显卡 5步轻松搞定【图文教程】
  16. 星际争霸人族科技球介绍
  17. HDMI 2.0概述
  18. python英雄联盟脚本是什么_Python3爬取英雄联盟英雄皮肤大图实例代码
  19. Qt学习笔记——获取本机网络信息(IP, 子网掩码, 广播地址,主机名
  20. iOS 苹果授权登录(Sign in with Apple)

热门文章

  1. matlab 工具箱下载地址
  2. js验证身份证(详细版)
  3. 关于Qt4K高分屏自适应问题
  4. 北大数学英才班,没有一名新生经历高三
  5. mysql一对多查询_MySQL 一对多查询
  6. 电子漫画系列更新10张!古老的示波器,USB hub萌妹,超级酷的焊接壁画
  7. 给移动硬盘装上LINUX全攻略,简单几步把Ubuntu装进移动硬盘引导使用
  8. 大数据—— Clickhouse 介绍与引擎的使用
  9. 学计算机Java和c语言哪个出路比较好
  10. 【职业经验】三年数据沉淀!2020 年度测试行业问卷调查结果火热出炉!