当当网中的邮件发送功能。
本身邮件发送功能不难,就是借助了工具类JavaMail的工具类,我们调用工具类的发送邮件的方法即可完成,方法的参数有收件人,邮件标题和邮件内容。 我们在包装这个功能的时候可以把他应用到一个场景里面。 比如:我们定期给用户发送网站营销的一些电子邮件。在营销推广模块,我们可以写出这个功能。
当当网中的支付宝支付功能。
- 在当当网里面引入jar包。
<dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.13.50.ALL</version> </dependency>
- 引入工具类。根据自己的支付宝沙箱环境修改工具类中的属性常量。
- 把工具类加入到spring中让,spring管理起来这个类。在spring配置文件中加入一个配置项
<!--让spring扫描工具中 的注解--> <context:component-scan base-package="com.baizhi.util"/>
- 写控制器类
@Autowired private AlipayTemplate at; @RequestMapping("pay") public void payMoney(HttpServletResponse response) throws Exception { PayVo pv=new PayVo(); pv.setOut_trade_no("200"); pv.setSubject("aaaa"); pv.setTotal_amount("100"); //告知浏览器响应的是一个html字符串 response.setContentType("text/html"); String pay = at.pay(pv);//支付的html源码 response.getWriter().print(pay); }
- 测试,输入控制器地址会到支付页面。
使用SpringAop和RabbitMQ实现日志的记录功能。
思路:
操作步骤:
- 建表,把日志表创建出来。
- 用mybatisplus生成log模块的增删改查基础代码。
- 把日志模块的springboot环境搭建起来。创建配置文件,创建入口类。
- 到商品模块中给业务类增加额外功能。LogAspect(springaop的额外增强类。)。
- LogAspect中准备log对象的各个属性值。封装到log对象里面。把log对象转换为一个json串,通过rabbitmq的生产者代码把json串作为一个消息发给rabbitmq的消息队列。
- 在日志模块中增加一个监听消息队列收到消息的监听器。这个监听器就是rabbitmq的消费者。消费者收到消息后,将json串转换为log对象,然后调用业务类的插入方法把日志对象存储到数据库里面。
Redis中秒杀的功能。
商城项目中有个秒杀功能,一元抢iphone手机。商城有十台iphone要促销,吸引顾客。现在要抢的人比较多。并发量大。
- 第一版代码
@GetMapping("/secKill") public String secKill(int pid){ String key="product:"+pid; //获取到操作string的工具类 ValueOperations<String, Object> stringObjectValueOperations = redisTemplate.opsForValue(); //获取到redis里面商品的数量 Integer count = (Integer)stringObjectValueOperations.get(key); //判断商品数量是否大于0 if(count>0){ //大于0了就对商品数量减一 stringObjectValueOperations.decrement(key); System.out.println("购买成功,还剩"+(count-1)+"个"); return "购买成功,还剩"+count+"个"; } System.out.println("购买失败"); return "购买失败"; }
第一版代码在多线程的环境中运行是有问题的。
- 第二版使用redis的乐观锁
@GetMapping("/secKill2") public String secKill2(int pid){ //1.先获取redis里面的库存数量 String key="product:"+pid; //开启一下事务 redisTemplate.setEnableTransactionSupport(true); redisTemplate.watch(key); ValueOperations<String, Object> stringObjectValueOperations = redisTemplate.opsForValue(); Integer count = (Integer) stringObjectValueOperations.get(key); redisTemplate.multi(); //2.判断是否大于0 if (count > 0) { //让库存减一,把库存减一放入到一个事务里面,然后监听product:1这个键是否被其他事务修改 stringObjectValueOperations.decrement(key); } List<Object> execList = redisTemplate.exec();//真正执行事务 if(execList!=null&&execList.size()!=0){ System.out.println("购买成功!还有" + (count - 1) + "个"); return "购买成功!还有" + (count - 1) + "个"; }else{ System.out.println("购买失败"); return "购买失败"; } }
第二版的代码会出现很多人抢商品但是到最后商品还有留存的问题。
- 第三版使lua脚本实现原子操作
lua脚本:
local prodid=KEYS[1]; local qtkey="product:"..prodid; local num= redis.call("get" ,qtkey); if tonumber(num)<=0 then return 0; else redis.call("decr",qtkey); end return tonumber(num);
@GetMapping("/secKill3") public String secKill3(int pid){ long result = 0; try { //调用lua脚本并执行 DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(); redisScript.setResultType(Long.class);//返回类型是Long //lua文件存放在resources目录下 redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("test.lua"))); result = redisTemplate.execute(redisScript, Arrays.asList(pid+"")); } catch (Exception e) { e.printStackTrace(); } if(result>0){ System.out.println("购买成功,还剩"+(result-1)+"个"); return "购买成功,还剩"+(result-1)+"个"; } System.out.println("购买失败"); return "购买失败"; }
Redis中验证码结合手机信息的验证功能。
要求:
- 收入手机号,点击发送后生成六位验证码,2分钟内有效
- 收入验证码点击验证,返回成功或者失败。
- 每个手机号每天只能输入三次。
//手机号 private static final String PHONE="15009876543"; //redis里面存储的两个key的名字,一个键叫15009876543:code,另一个叫15009876543:count private static final String CODEKEY=PHONE+":code"; private static final String COUNTKEY=PHONE+":count"; //验证手机跟用户输入的验证码是否一致 @Test public void verifyCode(){ String userCode="555305"; Jedis jedis = JedisUtil.getJedis(); String realCode = jedis.get(CODEKEY); jedis.close(); if(userCode.equalsIgnoreCase(realCode)){ System.out.println("验证码一致!"); }else{ System.out.println("验证码有误!"); } } @Test //给手机发信息,保证两分钟有效,保证一天发送三次 public void testSendMsg(){ //验证该手机号是否超过次数 Jedis jedis= JedisUtil.getJedis(); String countValue = jedis.get(COUNTKEY);//从redis里面获取到该手机号当天发了几次。 if(countValue==null){ //设置key和值的同时,给键一个超时时间expire jedis.setex(COUNTKEY,24*60*60,"1"); }else{ int count = Integer.parseInt(countValue); //一天还没有超过3次 if(count<3){ jedis.incr(COUNTKEY);//让redis的countkey自增一下 }else{//超过三次 System.out.println("一天不能超过三次"); return; } } String code = generateCode();//生成验证码 //调用工具类给手机发信息 System.out.println("给手机发送了一个验证码:"+code); jedis.setex(CODEKEY,120,code);//把验证码放入到了redis里面。 jedis.close(); } public String generateCode(){ Random random=new Random(); StringBuilder result=new StringBuilder(); for(int i=0;i<6;i++){ int num = random.nextInt(10); result.append(num); } return result.toString(); }
用户模块认证和授权功能。
主要的表有三个:用户表(bz_admin),角色表(bz_role),菜单表(bz_menu).
用户表和角色表之间有多对多关系。关系表:bz_admin_role
角色表和菜单表有多对多关系。关系表:bz_role_resource
商品模块的商品属性表的设计
商品模块复杂的是表设计,最初设计是只有一个张商品表。但是有一个问题就是不同类型的商品属性是不一样的。比如手机有cpu和屏幕这些属性,但是衣服没有,衣服有颜色,尺寸,布料这些属性。一个大型商城系统需要很多类型的商品。所以不适合建到一张表里面。 我们为了能够灵活存储商品属性我们又设计了一个属性表,来存储各个类型的商品都有哪些不同的属性,一个属性就对应属性表里面的一行数据。 然后我们需要存储每个商品的商品属性值,这时候我们又创建了一个属性值表。来存储每个属性对应的属性值。 这样就巧妙地把商品表中的列转换成了另外两张表的行数据。
修改后一张表被改为下面四张表
ES的站内分词高亮查询功能。
seata实现分布式事务,下订单时库存和订单的事务问题。
- 一阶段:执行本地事务,并返回执行结果
- 二阶段:根据一阶段的结果,判断二阶段做法:提交或回滚
Seata在第一极端先做用户的sql,在做用户的sql前会记录数据日志信息。当需要回滚的的时候seata会将数据进行自动恢复,不需要程序写补偿代码。
使用分布式锁解决缓存击穿的功能。
redis分布式锁代码
/** * 分布式锁 */ public List<Node> getDataByRedisLock(){ List<Node> nodeList = null; //生成UUID String uuid = UUID.randomUUID().toString(); //1.获取锁 执行业务代码 set nx (setIfAbsent) //setIfAbsent 该方法如果设置了过期时间 底层就是 set Ex nx 加锁和过期时间设置是原子性的 Boolean lock = redisTemplate.opsForValue().setIfAbsent("redis-lock", uuid,100,TimeUnit.SECONDS); if (lock){ System.out.println("获取锁"); //加锁成功查询数据库 try { nodeList = getNodesByMysql(); }finally { //查询完成解锁 String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end"; /** * 参数1 脚本 * 参数2 要删除的key * 参数3 uuid */ redisTemplate.execute( new DefaultRedisScript<Long>(script,Long.class), Arrays.asList("redis-lock"), uuid ); } }else { try { System.out.println("没有获取锁重试"); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //重试 getDataByRedisLock(); } return nodeList; }
EasyPoi的excel导入导出功能。
在项目中使用excel导入导出是一个很常用的功能,比如到导出商品类别信息或者导出用户信息到excel里面,都可以作为项目开发经验写到简历里面。
使用步骤:
- 实体类加注解
@Data public class BzBrand implements Serializable { @Excel(name = "品牌id") private Long brandId; @Excel(name = "品牌名字") private String name; @Excel(name = "首字母") private String firstLetter; @Excel(name = "显示状态") private Integer showStatus; }
- 直接完成导入导出
导出:
/** * 导出 */ @Test public void test1() throws Exception { /** * 准备数据 */ List<BzBrand> BzBrands = brandMapper.selectAll(); /** * 导出参数对象 * 参数1 标题 * 参数2 表名 */ ExportParams params = new ExportParams("所有的品牌数据","brands"); Workbook workbook = ExcelExportUtil.exportExcel(params, BzBrand.class, BzBrands); workbook.write(new FileOutputStream("E://easyBrands.xls")); }
导入:
/** * 导入 */ @Test public void test2() throws Exception { FileInputStream inputStream = new FileInputStream("E://easyBrands.xls"); /** * 导入参数对象 * setTitleRows 声明标题占有的行数 * setHeadRows 声明表头占有的行数 */ ImportParams importParams = new ImportParams(); importParams.setTitleRows(1); importParams.setHeadRows(1); /** * 导入方法 * 参数1 流 * 参数2 类对象 * 参数3 导入参数对象 */ List<BzBrand> BzBrands = ExcelImportUtil.importExcel(inputStream, BzBrand.class, importParams); for (BzBrand brand : BzBrands) { System.out.println(brand); } }
- 值的替换
当导出数据时,需要对数据进行转换,比如显示状态 (0->不显示,1->显示)。就需要在导出时,对值进行替换。
@Data public class BzBrand implements Serializable { @Excel(name = "品牌id") private Long brandId; @Excel(name = "品牌名字") private String name; @Excel(name = "首字母") private String firstLetter; @Excel(name = "显示状态",replace={"不显示_0","显示_1"}) private Integer showStatus; }
拍卖系统的拍卖功能
拍卖系统的主要表的设计:
create table t_auction( auctionId int primary key auto_increment, -- 主键 编号 auctionName varchar(500), -- 拍卖品名称 auctionStartPrice double, -- 拍卖品起拍价 auctionUpset double, -- 拍卖品底价 auctionStartTime date, -- 开始时间 auctionEndTime date , -- 结束时间 auctionPic varchar(500), -- 拍卖品老图片 auctionDesc varchar(200) -- 拍卖品描述 ); create table auction_user( userId int primary key auto_increment, -- 主键 userName varchar(50),-- 用户名 userPassword varchar(50),-- 用户密码 userIsadmin int -- 是否为管理员 0普通用户 1管理员 ); create table auction_record( record_id int primary key auto_increment, -- 主键 record_time timestamp,-- 竞拍时间 record_price double, -- 竞拍出价 user_id int , -- 用户编号 外键 (用户表) auction_id int, -- 拍卖品编号 外键(拍卖品表) );
学校的评论功能
评论系统的主要表设计:
create table t_school( id int primary key auto_increment, name varchar(30) not null, description varchar(200) not null ); -- 学校和评论外键 用户和评论外键 create table t_user( id int primary key auto_increment, username varchar(30) not null, password varchar(30) not null ); -- 点赞方法redis,评论下的评论用一级类别二级类别 create table t_comment( id int primary key auto_increment, pid int, content varchar(255) not null, sid int not null, uid int not null, create_time date not null );