界面效果
用户管理界面
详情页面
编辑页面
删除功能
用户界面
查询余额
取出余额
存款
转账
后端包结构
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")
@TableId
private 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;
@Bean
public 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 {
@Bean
public 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 {
@Bean
public 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 {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public 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 {
@Autowired
private 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 {
@Autowired
private 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;
}
@Override
public 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;
}
@Override
public 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 {
@Override
public 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();
@Autowired
private BankUserMapper bankUserMapper;
@Autowired
private AdminUserMapper adminUserMapper;
@Autowired
private RedisUtil redisUtil;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result queryUserByPage(long page, long pageSize) {
Page<BankUser> pObj = new Page<>(page, pageSize);
IPage<BankUser> userIPage = bankUserMapper.selectPage(pObj, null);
return Result.success(userIPage);
}
@Override
public Result queryUserById(String id) {
BankUser bankUser = queryUserMutex(id);
return Result.success(bankUser);
}
@Override
public Result deleteUserById(String id) {
AdminService proxy = (AdminService) AopContext.currentProxy();
String msg = proxy.deleteById(id) > 0 ? "删除成功" : "删除失败";
return "删除成功".equals(msg) ? Result.success(msg) : Result.error(msg);
}
@Override
public 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);
}
@Transactional
public int deleteById(String id) {
int result = bankUserMapper.deleteById(id);
if (result > 0) {
stringRedisTemplate.delete(CACHE_USER_KEY + id);
}
return result;
}
@Transactional
@Override
public 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("更新失败");
}
@Override
public 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("用户名或密码错误");
}
@Override
public 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 {
@Autowired
private BankUserMapper bankUserMapper;
@Override
public 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("银行卡号或密码错误");
}
@Override
public 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("银行卡号或密码错误");
}
@Override
public 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
@Override
public 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 {
@Resource
private 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"
border
style="width: 100%">
<el-table-column
fixed
prop="cardId"
label="银行卡号"
width="auto">
</el-table-column>
<el-table-column
prop="userName"
label="用户名"
width="auto">
</el-table-column>
<el-table-column
prop="account"
label="账户余额"
width="auto">
</el-table-column>
<el-table-column
fixed="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 = row
this.dialogFormVisible = true
},
handleUpdate() {
if (this.form.cardId !== undefined) {
console.log("更新操作")
this.dialogFormVisible = false
console.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 = false
console.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 = true
this.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.current
this.tableData = res.data.msg.records
this.pageSize = res.data.msg.size
this.dataTotal = res.data.msg.total
if (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/value
params.append('adminPwd', this.ruleForm.password); //你要传给后台的参数值 key/value
this.$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>