前言
-
时间:2021.8.6—8.21
-
内容:
- 关于映射会出的几点问题。(映射1)
- 小众网前端:
- 1 点击列表的某项书跳转至该书的详情页面。(联表查询)
- 2 可指定地址进入注册页面。(映射2)
- 3 注册页面有验证码。
- 4 md5加密。(用到了盐值)
- 5 图书状态(想看/已看)、点赞、短评
-
备注:
-
有些是8月21日才补充的笔记所以这篇看起来就乱了很多…前半部分自己的思考还是蛮有用滴~~(真是拖延癌晚期患者了…
-
另外关于这个小项目,暂时不更新啦,代码已贴GitHub~~~~
-
1 详情页面(联表查询)
-
这里就直接贴代码带过吧,就是前面写无数遍ssm的基础操作,难点就是一个联表查询。
-
思考结果:
-
一般来说,我们是可以在控制层任意调用业务的,控制层的分类只是为了看起来清晰、更好理解一些。(提高可读性
-
初学时的惯有思维,一个dao对应一个domain,一个service对应一个dao,但实际应用上~前者没啥问题,dao现在继承于baomidou的各种sql方法,不用自己写了;后者却不是,就比如联表查询的时候就会出现,一个service对应多个dao。
@Service @Transactional(propagation = Propagation.NOT_SUPPORTED,readOnly = true) public class EvaluationServiceImpl implements EvaluationService { @Autowired EvaluationDao evaluationDao; //对会员和书下手,需要注入这两个 @Autowired BookDao bookDao; @Autowired MemberDao memberDao; @Override public List<Evaluation> selectEvaluationList(Long bookId) { QueryWrapper<Evaluation> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("bookId",bookId); queryWrapper.eq("statu","enable"); queryWrapper.orderByDesc("createTime"); //查出所有评论,针对这本书的 List<Evaluation> evList = evaluationDao.selectList(queryWrapper); //书有了 Book book = bookDao.selectById(bookId); for(Evaluation evaluation : evList){ //会员有了 Member member = memberDao.selectById(evaluation.getMemberId()); evaluation.setBook(book); evaluation.setMember(member); } return evList; } }
-
对应该业务同名包要加东西,感觉逻辑不太对,但总结一下来看好像也没啥不对hhh:评论表的数据库里是只有用户id没有用户信息的,那页面想要获取用户信息怎么办呢?发一个评论,没有发评论的名字总不对劲吧!所以怎么办呢?通俗说就是我们的联表查询,但这个联表查询是java后端方面的,不像以前攻一攻sql就能解决。
-
太多废话了,步骤如下:
-
domain页面,类别里增加一个属性!!!
@TableName public class Evaluation { @TableId(type = IdType.AUTO) @TableField("evaluationId") private Long evaluationId; private Long bookId; private Long memberId; private String content; private Integer score; private Date createTime; private Integer enjoy; private String statu;//状态 审核状态,enable 有效,disable 无效 private Date disableTime; private String disableReason; //我们需要在这里,增加一些和表不对应的属性 @TableField(exist = false)//表示 数据库表格中 不存在对应的字段,不会参与到sql的自动生成 private Book book;//这个对象是由我们手动来查的 @TableField(exist = false) private Member member;
-
-
2 映射
2-1 映射1
突然提起映射的问题,主要是遇上蛮多次ssm和某某视图view不匹配的情况,前面的总结提过,现在再补充个小结~
- 有可能是dao的不匹配:dao需要同名对应一个.java一个.xml,并且xml内的mapper空间需要写对应的java包路径。
- 有可能是applicationContext.xml里包问题:要么是扫描时候范围太小了,推荐扫com.pro,可以扫所有的@Controller和@Service;要么是类似我这种懒癌cv选手,在复制粘贴包的的时候,过于智能的idea帮我把路径改了(我该感谢它么??万里代码找不同…找着这个配置文件多了个com,ctrl+f去查找替换才解决)
- 有可能是控制器里的路径映射不到对应的页面:详见映射2
2-2 映射2
-
思考过程:
- 之前一直以为不加@ResponseBody这种搞地址的方式时,mapping里的参数要和方法名一样,今天突然发现是可以不一样的!
- @GetMapping里的地址只是单纯用于地址栏的输入(以及前端找这个方法时候要喊的名字),随便写啥都行。
- 方法名可以和@GetMapping的名字不一样,就像一个人除了自己的名字之外可以有外号一样,方法也是可以有专属自己的外号的,区别在于这个外号只能有一个(总不能在上头加两个GetMapping?(好像也不是不行…?(困惑(狗头
- 另外关于GetMapping和PostMapping突然有些迷糊了,如果是直接在地址栏输入地址的,那是不是两种方式都可以呢?毕竟也没传数据过来,所以…get和post是不是只影响了“从前端ajax传数据过来”这种情况,对于“地址栏直接喊名字访问”这种情况是不影响的呢?(困惑)
- 以及关于map,这个熟悉的陌生人,一直不太明明白它的机制,等后面慢慢理清了再小结下噢!!!!!!!
- 之前一直以为不加@ResponseBody这种搞地址的方式时,mapping里的参数要和方法名一样,今天突然发现是可以不一样的!
-
以“在地址栏输入指定地址跳转至注册页面”为例:
-
方法①
@GetMapping("/a") public String b(){ return "reg"; }
-
方法②
@GetMapping("/reg.html") public ModelAndView toReg() { ModelAndView mav = new ModelAndView("reg"); return mav; }
-
方法③ 返回页面名字
ModelMap mm = new ModelMap();
-
方法④ 返回页面名字(这里写的返回参数应该是String,搞不太清,重新写笔记只分的清前两种惹…)
public ModelAndView displayBookDetail(@PathVariable('bookId')Long bookId,Mode model){ Book book = bookService.getBookId(bookId); model.addAttribute("book",book); }
-
3 验证码
3-1 验证码(显示)
pom.xml
- maven里,只有这一个版本的噢!
<!--验证码-->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
applicationContext.xml
-
增加一个bean
<!--配置kaptcha--> <bean id="defaultKaptcha" class="com.google.code.kaptcha.impl.DefaultKaptcha"> <property name="config"> <bean class="com.google.code.kaptcha.util.Config"> <constructor-arg><!--构造函数--> <props> <!--边框--> <prop key="kaptcha.border">no</prop> <!--宽度120px--> <prop key="kaptcha.image.width">120</prop> <!--颜色--> <prop key="kaptcha.textproducer.font.color">pink</prop> <!--大小--> <prop key="kaptcha.textproducer.font.size">40</prop> <!--几个字符--> <prop key="kaptcha.textproducer.char.length">4</prop> </props> </constructor-arg> </bean> </property> </bean>
KaptchaController.java
@Controller
public class KaptchaController {
@Autowired
protected Producer defaultKaptcha;
@GetMapping("/verifyCode")
public void createVerifyCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setDateHeader("Expires",0);//响应立即过期
//不缓存图片
response.setHeader("Cache-Control", "no-store,no-cache,must-revalidate");
response.setHeader("Cache-Control", "post-check=0,pre-check=0");
response.setHeader("Pragma", "no-cache");
response.setContentType("image/png");
//生成验证码文本
String verifyCode = defaultKaptcha.createText();
request.getSession().setAttribute("verifyCode", verifyCode);
//这里打印出来,一会试验时候,看看对比一下,是否一致
System.out.println(request.getSession().getAttribute("verifyCode"));
//变魔术,变成图片
BufferedImage image = defaultKaptcha.createImage(verifyCode);
//输出流,从服务器到客户端,架了一个管道,将那个图片验证码,发送到你的页面中
ServletOutputStream outputStream = response.getOutputStream();
//写,把图通过管道,写到你的客户端
ImageIO.write(image, "png", outputStream);
outputStream.flush();
outputStream.close();
//io流。什么是流,有哪些流?
}
}
-
这里的Producer不是自己写的类,是kaptcha里的一个类
package com.google.code.kaptcha; import java.awt.image.BufferedImage; public interface Producer { BufferedImage createImage(String var1); String createText(); }
3-2 验证码(验证)
-
主要是前端的操作啦,因为做笔记和写代码时间隔的有丢丢久
-
登录的验证
<script> //点击验证码图片刷新验证码 $("#imgVerifyCode").click(function () { reloadVerifyCode(); }); //重新发送请求,刷新验证码 function reloadVerifyCode(){ //这里实现刷新验证码,请求中设置时间差。 $('#imgVerifyCode').attr("src","verifyCode?tp="+new Date().getTime()); } //提交表单的操作 $(function () { $('#btnSubmit').click(function () { $.ajax({ url:'/xzw/checkLogin', type:'post', dataType:'json', data:$('#frmLogin').serialize(), success:function (data) { if(data.code=="ok_vc"){ alert(data.msg); //去首页 window.location="/xzw/index.html?tp="+new Date().getTime(); }else { //刷新页面,重新登录 alert(data.msg); $('#password').val("") $('#nickname').val("") $('#verifyCode').val("") reloadVerifyCode(); } } }); }); }); </script>
-
注册的验证
<script> //点击验证码图片刷新验证码 $("#imgVerifyCode").click(function () { reloadVerifyCode(); }); //重新发送请求,刷新验证码 function reloadVerifyCode(){ //请在这里实现刷新验证码,请求中设置时间错。 $('#imgVerifyCode').attr("src","verifyCode?tp="+new Date().getTime()); } //提交表单的操作 $("#btnSubmit").click(function () { //表单校验 var username = $.trim($("#username").val()); var regex = /^.{6,10}$/; if (!regex.test(username)) { alert("用户名请输入正确格式(6-10位)"); return; } var password = $.trim($("#password").val()); if (!regex.test(password)) { alert("密码请输入正确格式(6-10位)"); return; } $btnReg = $(this); $btnReg.text("正在处理..."); $btnReg.attr("disabled", "disabled"); //发送ajax请求 $.ajax({ url: "regist", type: "post", dataType: "json", data: $("#frmLogin").serialize(), success: function (data) { //结果处理,根据服务器返回code判断服务器处理状态 //服务器要求返回JSON格式: //{"code":"0","msg":"处理消息"} console.info("服务器响应:" , data); if (data.code == "ok_vc") { //显示注册成功对话框 alert(data.msg) $('#username').val("") $('#password').val("") $('#nickname').val("") $('#verifyCode').val("") reloadVerifyCode(); } else { //服务器校验异常,提示错误信息 alert(data.msg); //错误,则重新生成验证码 reloadVerifyCode(); } } }); return false; }); </script>
4 md5加密
-
这个加密技术是用来干啥的呢?我们可以想一下噢,如果我们注册账号的时候,填的密码是明明白白进入数据库的,那我们的账号密码岂不是被数据库管理员看光光惹?所以,为了用户和开发彼此的信任,就出现了这种加密的中间环节:用户上传密码时候会给密码加密,所以管理员端收到的用户密码是一串很长很长的加密过的字符串,而用户登录账号去和后台匹配密码时候,也会用同样的方式加密密码再匹配。
-
这个地方有个小小bug,比如说我的密码是123456,再申请了个别的账号密码也是123456,那么记录进数据库的两条密码也是相同的字符串。。。
-
代码如下:
package com.pro.util; import org.apache.commons.codec.digest.DigestUtils; /** * @author Yuhua * @since 21.8.5 11:20 */ public class MD5Util { /** * * @param source 准备加密梳理的 * @param salt * @return */ public static String md5Digest(String source,Integer salt){ char[] ca = source.toCharArray(); /*for (int i = 0; i < ca.length; i++) { ca[i] = (char) (ca[i]+salt); }*/ //字符数组-->字符串 String target = new String(ca); String md5 = DigestUtils.md5Hex(target); //abcd-->adfadsfasdfadfsa return md5; } }
-
MemberServiceImpl.java
//注册 @Override public Member createMember(String username, String password, String nickname){ QueryWrapper queryWrapper = new QueryWrapper(); queryWrapper.eq("username",username); List<Member> memberList = memberDao.selectList(queryWrapper); //根据username,如果查到了,说明已经被注册了 if(memberList.size()>0){ //不能注册 throw new ServiceException("801","该用户名已被注册了!"); } // Member member = new Member(); member.setUsername(username); member.setNickName(nickname); //密码域 int salt = new Random().nextInt(1000) + 1000; String md5 = MD5Util.md5Digest(password,salt); member.setPassword(md5); member.setSalt(salt); member.setCreateTime(new Date()); System.out.println(member.toString()); //添加 memberDao.insert(member); return member; }
//登录,查用户及密码,是否正确 @Override public Member checkLogin(String username, String password){ QueryWrapper<Member> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("username",username); Member member = memberDao.selectOne(queryWrapper); if(member==null){ throw new ServiceException("802","用户不存在"); } //可以站到这里,说明表中有这个用户 String md5 = MD5Util.md5Digest(password, member.getSalt()); if(!md5.equalsIgnoreCase(member.getPassword())){ throw new ServiceException("803", "输入密码不对"); } //如果代码可以走到这里,说明都正确 return member; }
5 图书状态、点赞、短评
-
这部分直接上代码叭。。不全的直接去GitHub上搞来康康。。。地址贴在文章开头惹。。。
<script> $.fn.raty.defaults.path = '/xzw/resources/raty/lib/images'; $(function () { $(".stars").raty({readOnly: true}); }) $(function () { //注意,注释 <!-- 状态不为空,则设置高亮,将查出的阅读状态对应的元素,加为高亮 ??这俩问号表示不为null--> <#if memberReadState ??>/*如果状态存在*/ $('[data-read-state="${memberReadState.readState}"]').addClass('highlight'); </#if> //如果没有登录,则阅读状态,写评论,点赞,都不能操作 <#if !loginMember ??> $('[data-read-state],#btnEvaluation,[data-evaluation-id]').click(function () { alert("请先登录,才能操作!") }) </#if> <#if loginMember ??> $('[data-read-state]').click(function () { var readState = $(this).data('read-state'); //post是4个参数 $.post('/xzw/updateReadState',{ "memberId":${loginMember.memberId}, "bookId":${book.bookId}, "readState":readState },function () { if(data.code=="ok_mrs"){ $('[data-read-state]').removeClass('highlight');//清除高亮 $('[data-read-state="'+readState+'"]').addClass('highlight'); } },'json'); window.location.reload(true); }); //点击短评按钮 $("#btnEvaluation").click(function () { $('#score').raty({});//将span转为星形组件 //显示写短评div $('#dlgEvaluation').modal('show'); }) //提交点评 $("#btnSubmit").click(function () { var score = $('#score').raty("score"); var content = $('#content').val(); //没有内容,不提交 if(score==0||$.trim(content)==""){ return; } $.post("/xzw/evaluate",{ "score":score, "bookId": ${book.bookId}, "memberId":${loginMember.memberId}, "content":content },function (data) { if(data.code=="ok_ev"){ //重新加载当前页,得到最新的评论 window.location.reload(); } },'json') }); $('[data-evaluation-id]').click(function(){ var evaluationId = $(this).data("evaluation-id"); $.post('/xzw/enjoy',{"evaluationId":evaluationId},function (data) { if(data.code=="ok_en"){ window.location.reload();//这句话好像没啥用?!! $('[data-evaluation-id="'+evaluationId+'"] span').text(data.evaluation.enjoy) } },'json'); }); </#if> }); </script>
前端的一个东西
display:none和jQuery的show是对应的