测试地址:https://blog.csdn.net/weixin_42717928/article/details/105212776
开始做毕业设计了,记录一下最后一次做项目了,唉
时间:2019/12/06
Day 06
今晚就设计了一个表
user_info用户表
字段名称 |
类型 |
是否主键 |
是否为空 |
字段描述 |
userName |
varchar(10) |
Y |
N |
用户昵称 |
userPassword |
varchar(20) |
N |
N |
用户密码 |
userPhone |
varchar(20) |
Y |
N |
用户手机号 |
userPicPath |
varchar(20) |
N |
N |
用户头像虚拟路径 |
userLocPath |
varchar(20) |
N |
N |
用户头像真实路径 |
userPS |
varchar(20) |
N |
Y |
用户个性签名 |
userOriginal |
bigint |
N |
Y |
用户原创文章数 |
userFollowers |
bigint |
N |
Y |
用户被关注人数 |
userFollowing |
bigint |
N |
Y |
用户关注人数 |
userLoginTime |
datetime |
N |
Y |
用户登录时间 |
userRole |
int |
N |
Y |
用户权限 |
原创文章,关注人数,被关注人数初始值是0
个性签名初始值:喜欢是淡淡的爱;爱是深深的喜欢
权限:null是普通用户,1是VIP用户,2是管理员
头像虚拟路径初始值:/statics/uploadfiles/tx.jpg
头像真实路径初始值:
F:\footprint\out\artifacts\GZLifeCircle_war_exploded\statics\uploadfiles\tx.jpg
Day 09
CREATE DATABASE GZLifeCircle
CREATE TABLE user_info(
userName VARCHAR(20) NOT NULL,
userPassword VARCHAR(20) NOT NULL,
userPhone VARCHAR(20) NOT NULL,
userPicPath VARCHAR(500) NOT NULL DEFAULT '/statics/uploadfiles/tx.jpg',
userLocPath VARCHAR(500) NOT NULL DEFAULT 'F:\\GZLifeCircle\\out\\artifacts\\GZLifeCircle_war_exploded\\statics\\uploadfiles\\tx.jpg',
userPS VARCHAR(40) DEFAULT '喜欢是淡淡的爱;爱是深深的喜欢',
userOriginal BIGINT(30) DEFAULT 0,
userFollowers BIGINT(30) DEFAULT 0,
userFollowing BIGINT(30) DEFAULT 0,
userGoods BIGINT(30) DEFAULT 0,
userLoginTime DATETIME,
userRole INT,
PRIMARY KEY (userPhone)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
注意:\是转义字符,所以为了显示\,要\\
user_info用户表
字段名称 |
类型 |
是否主键 |
是否为空 |
字段描述 |
userName |
varchar(20) |
N |
N |
用户昵称 |
userPassword |
varchar(20) |
N |
N |
用户密码 |
userPhone |
varchar(20) |
Y |
N |
用户手机号 |
userPicPath |
varchar(500) |
N |
N |
用户头像虚拟路径 |
userLocPath |
varchar(500) |
N |
N |
用户头像真实路径 |
userPS |
varchar(40) |
N |
Y |
用户个性签名 |
userOriginal |
Bigint(30) |
N |
N |
用户原创文章数 |
userFollowers |
Bigint(30) |
N |
N |
用户被关注人数 |
userFollowing |
Bigint(30) |
N |
N |
用户关注人数 |
userGoods |
Bigint(30) |
N |
N |
用户文章获取的点赞数 |
userLoginTime |
datetime |
N |
Y |
用户登录时间 |
userRole |
int |
N |
Y |
用户权限 |
原创文章,关注人数,被关注人数初始值是0
个性签名初始值:喜欢是淡淡的爱;爱是深深的喜欢
权限:null是普通用户,1是VIP用户,2是管理员
头像虚拟路径初始值:/statics/uploadfiles/tx.jpg
头像真实路径初始值:
F:\footprint\out\artifacts\GZLifeCircle_war_exploded\statics\uploadfiles\tx.jpg
用户昵称:注册时候会验证唯一
登录页面
注册页面
Day 10
对注册的内容限制一下
昵称:4-8位字符串
密码:8-16位有字母和数字组成
手机格式要符合
// 注册-----------------------------------------------注册//
// trim:去掉两端空格,中间的不会去掉;replace里面的写法去掉中间的空格
$(document).ready(function() {
$("#userName").blur(function () {
if($("#userName").val().trim().replace(/\s/g,"")==''||$("#userName").val().trim()==null){
$('#userName').css({
border: "1px solid red",
boxShadow: "0 0 2px red"
});
$('#userCue').html("×用户昵称不能为空");
userNameF=false;
}else{
if($("#userName").val().length<4||$("#userName").val().length>8){
userNameF=false;
$('#userName').css({
border: "1px solid red",
boxShadow: "0 0 2px red"
});
$('#userCue').html("×用户名4-8位");
}else {
// userName();
}
}
});
$("#userPassword").blur(function () {
if($("#userPassword").val()==null){
$('#userPassword').focus().css({
border: "1px solid red",
boxShadow: "0 0 2px red"
});
$('#userCue').html("×密码不能为空");
userPasswordF=false;
}else if (!/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$/.test($('#userPassword').val())){
$('#userPassword').css({
border: "1px solid red",
boxShadow: "0 0 2px red"
});
$('#userCue').html("×密码8-16有字母和数字组成");
userPasswordF=false;
}else {
$('#userPassword').css({
border: "1px solid #D7D7D7",
boxShadow: "0 0 2px #D7D7D7"
});
$('#userCue').html("注册注意:");
userPasswordF=true;
}
});
$("#confirmUserP").blur(function () {
if($('#confirmUserP').val() != $('#userPassword').val()){
$('#confirmUserP').css({
border: "1px solid red",
boxShadow: "0 0 2px red"
});
$('#userCue').html("×两次密码不一致");
userPasswordF=false;
}else {
$('#confirmUserP').css({
border: "1px solid #D7D7D7",
boxShadow: "0 0 2px #D7D7D7"
});
$('#userCue').html("注册注意:");
userPasswordF=true;
}
});
$("#userPhone").blur(function () {
if($("#userPhone").val().trim().replace(/\s/g,"")==''||$("#userPhone").val().trim()==null){
$('#userPhone').css({
border: "1px solid red",
boxShadow: "0 0 2px red"
});
$('#userCue').html("×手机号不能为空");
userPhone=false;
}else if(!/(^1[3|4|5|7|8]\d{9}$)|(^09\d{8}$)/.test($("#userPhone").val())){
$('#userPhone').css({
border: "1px solid red",
boxShadow: "0 0 2px red"
});
$('#userCue').html("×手机号格式不对");
userPhoneF=false;
}else{
// userPh();
}
})
Day 12
上次对注册的内容用js做了限制
但是我还想,注册要保证昵称和手机号是唯一的
于是在原来那个方法成功的地方加了方法
// 注册时候判断用户昵称是否已存在
function userNameExist() {
$.ajax({
type:"POST",//请求类型
url:"userNameExist.json",//请求的url
data:{userName:$("#userName").val()},//请求参数
dataType:"json",//ajax接口(请求url)返回的数据类型
success:function(data){//data:返回数据(json对象)
if(data.userName == "exist"){//账号不可用,错误提示
$('#userName').css({
border: "1px solid red",
boxShadow: "0 0 2px red"
});
$('#userCue').html("×用户名已存在");
userNameF=false;
}else{//账号可用,正确提示
$('#userName').css({
border: "1px solid #D7D7D7",
boxShadow: "0 0 2px #D7D7D7"
});
$('#userCue').html("注册注意:昵称可用√");
userNameF=true;
}
},
error:function(data){//当访问时候,404,500 等非200的错误状态码
alert("请求错误!");
}
});
}
因为昵称和手机号方式是一样的,我方法就写在了一起
/**
* 注册时候根据userName查找UserInfo是否存在
* 注册时候根据userPhone查找UserInfo是否存在
* @param userName
* @return
* @throws Exception
* */
public UserInfo getUserInfo(String userName,String userPhone)throws Exception;
效果就出来了
Day 16
想了一下,不想给名字限制我要做的内容,于是换了个名字,改成了知援无境
写验证码遇到一个问题,不知道为什么我写sendMessage没反应,加了个1却可以了,有点迷
直接点击获取验证码会有提示
Day 19
注册的js验证写完了,中途时候出现了两个问题
一个是获取验证码后手机号改变了没有验证
一个是先点了确认密码再点密码,会出现提示密码不规范
短信效果就是这样:
主要的js和jsp页面代码
jsp代码如下:
js代码如下:
// 提交时候判断
function binding(){
var userPhone = $.trim($('#userPhone').val());
var code = $.trim($('#code').val());
alert("userPhone"+userPhone)
alert("code"+code)
if (!phoneReg.test(userPhone)) {
alert("请输入正确的手机号码")
}
else if (code.trim()==null||code.trim()==""){
alert("请输入验证码")
}
else if(codeApi==null||codeApi==""){
alert("验证码不规范,请点击获取验证码")
}
else{
if (codeApi.toString()=="shibai") {
alert("手机已经被注册")
codeApi=null;
}
else if (phoneNum.toString()!=userPhone){
alert("输入的手机号已经改变,请重新获得验证码")
phoneNum=null;
codeApi=null;
}
else if(codeApi.toString()!=code.toString()){
alert("验证码错误")
}else {
if (!userNameF){
alert("昵称不规范")
}
else if (!userPasswordF){
alert("密码不规范")
}
else if (!userPasswordCF){
alert("密码不一致")
}
else if (!userPhoneF){
alert("手机号不规范")
}
else {
if (confirm("是否提交?")) {
$("#regUser").submit();
}
}
}
}
}
function sendMessage1() {
curCount1 = count;//60秒数
var userPhone = $.trim($('#userPhone').val());
if (!phoneReg.test(userPhone)) {
alert("请输入有效的手机号码");
}else {
phoneNum=userPhone;//发送验证码的号码
codeCheck();//获得验证码
//设置button效果,开始计时
$("#btnSendCode").attr("disabled", "true");
$("#btnSendCode").val( + curCount1 + "秒再获取");
InterValObj1 = window.setInterval(SetRemainTime, 1000); //启动计时器,1秒执行一次
//向后台发送处理数据
}
}
function SetRemainTime() {
if (curCount1 == 0) {
window.clearInterval(InterValObj1);//停止计时器
$("#btnSendCode").removeAttr("disabled");//启用按钮
$("#btnSendCode").val("重新发送");
}
else {
curCount1--;
$("#btnSendCode").val( + curCount1 + "秒再获取");
}
}
function codeCheck() {
var userPhone = $.trim($('#userPhone').val());
$.ajax({
type : "POST",
url : "/qt/userCode.json",
data : 'userPhone=' + userPhone,
dataType : "json",
success:function(data) {
if (data.code=="shibai"){
alert("手机号已被注册,请更换手机号")
}
else {
codeApi=data.code;
}
},
error:function(data) {//当访问时候,404,500 等非200的错误状态码
alert("出错!")
}
})
}
// 注册-----------------------------------------------注册//
// trim:去掉两端空格,中间的不会去掉;replace里面的写法去掉中间的空格
$(document).ready(function() {
$("#userName").blur(function () {
if($("#userName").val().trim().replace(/\s/g,"")==''||$("#userName").val().trim()==null){
$('#userName').css({
border: "1px solid red",
boxShadow: "0 0 2px red"
});
$('#userCue').html("×用户昵称不能为空");
userNameF=false;
}else{
if($("#userName").val().length<4||$("#userName").val().length>8){
userNameF=false;
$('#userName').css({
border: "1px solid red",
boxShadow: "0 0 2px red"
});
$('#userCue').html("×用户名4-8位");
}else {
userNameExist();
}
}
});
$("#userPassword").blur(function () {
if($("#userPassword").val()==null){
$('#userPassword').focus().css({
border: "1px solid red",
boxShadow: "0 0 2px red"
});
$('#userCue').html("×密码不能为空");
userPasswordF=false;
userPasswordCF=false;
}else if (!/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$/.test($('#userPassword').val())){
$('#userPassword').css({
border: "1px solid red",
boxShadow: "0 0 2px red"
});
$('#userCue').html("×密码8-16有字母和数字组成");
userPasswordF=false;
userPasswordCF=false;
}else {
$('#userPassword').css({
border: "1px solid #D7D7D7",
boxShadow: "0 0 2px #D7D7D7"
});
$('#userCue').html("注册注意:密码可用");
userPasswordF=true;
userPasswordIs=true;
}
});
$("#confirmUserP").blur(function () {
if($('#confirmUserP').val() != $('#userPassword').val()){
$('#confirmUserP').css({
border: "1px solid red",
boxShadow: "0 0 2px red"
});
$('#userCue').html("×两次密码不一致");
userPasswordCF=false;
userPasswordF=false;
}else {
$('#confirmUserP').css({
border: "1px solid #D7D7D7",
boxShadow: "0 0 2px #D7D7D7"
});
$('#userCue').html("注册注意:密码一致");
userPasswordCF=true;
if (userPasswordIs){
userPasswordF=true;
}
}
});
$("#userPhone").blur(function () {
if($("#userPhone").val().trim().replace(/\s/g,"")==''||$("#userPhone").val().trim()==null){
$('#userPhone').css({
border: "1px solid red",
boxShadow: "0 0 2px red"
});
$('#userCue').html("×手机号不能为空");
userPhone=false;
}else if(!/(^1[3|4|5|7|8]\d{9}$)|(^09\d{8}$)/.test($("#userPhone").val())){
$('#userPhone').css({
border: "1px solid red",
boxShadow: "0 0 2px red"
});
$('#userCue').html("×手机号格式不对");
userPhoneF=false;
}else{
userPhoneExist();
}
})
});
// 注册时候判断用户昵称是否已存在
function userNameExist() {
$.ajax({
type:"POST",//请求类型
url:"userNameExist.json",//请求的url
data:{userName:$("#userName").val()},//请求参数
dataType:"json",//ajax接口(请求url)返回的数据类型
success:function(data){//data:返回数据(json对象)
if(data.userName == "exist"){//账号不可用,错误提示
$('#userName').css({
border: "1px solid red",
boxShadow: "0 0 2px red"
});
$('#userCue').html("×用户名已存在");
userNameF=false;
}else{//账号可用,正确提示
$('#userName').css({
border: "1px solid #D7D7D7",
boxShadow: "0 0 2px #D7D7D7"
});
$('#userCue').html("注册注意:昵称可用√");
userNameF=true;
}
},
error:function(data){//当访问时候,404,500 等非200的错误状态码
alert("请求错误!");
}
});
}
// 注册时候判断手机号是否已存在
function userPhoneExist() {
$.ajax({
type:"GET",//请求类型
url:"userPhoneExist.json",//请求的url
data:{userPhone:$("#userPhone").val()},//请求参数
dataType:"json",//ajax接口(请求url)返回的数据类型
success:function(data){//data:返回数据(json对象)
if(data.userPhone == "exist"){//账号不可用,错误提示
$('#userPhone').css({
border: "1px solid red",
boxShadow: "0 0 2px red"
});
$('#userCue').html("×手机号已存在");
userPhoneF=false;
}else{//账号可用,正确提示
$('#userPhone').css({
border: "1px solid #D7D7D7",
boxShadow: "0 0 2px #D7D7D7"
});
$('#userCue').html("注册注意:手机可用");
userPhoneF=true;
}
},
error:function(data){//当访问时候,404,500 等非200的错误状态码
alert("请求错误!");
return false;
}
});
}
数据库有了
不过页面还没写
Day 20
解决注册成功不跳登录页面
登录判断
// 登录-------------------------------------------登录
$(document).ready(function() {
$('#doLogin').click(function() {
if (($('#uPh').val() == "")||($('#uPh').val() == null)) {
$('#uPh').focus().css({
border: "1px solid red",
boxShadow: "0 0 2px red"
});
$('#doLoginError').html("×手机号不能为空");
return false;
}
else{
$('#uPh').css({
border: "1px solid #D7D7D7",
boxShadow: "0 0 2px #D7D7D7"
});
$('#doLoginError').html("注册注意:");
}
if (($('#uPa').val() == "")||($('#uPa').val() == null)){
$('#uPa').focus().css({
border: "1px solid red",
boxShadow: "0 0 2px red"
});
$('#doLoginError').html("×密码不能为空");
return false;
}
else {
$('#uPa').css({
border: "1px solid #D7D7D7",
boxShadow: "0 0 2px #D7D7D7"
});
$('#doLoginError').html("注册注意:");
}
$('#login_form').submit();
});
});
效果:
不过可惜要点击登录才进行判断,没有做成是聚焦那种效果,后续有空再改吧
再通过修改距离,显得空地方不这么大
主页没有做,随便写了个主页测试一下能不能登录进去以及数据对不对
补充一个知识点:
//转发,地址不变,共享数据,服务器直接访问目标地址的 url网址,把里面的东西读取出来,但是客户端并不知道
return "forward:/qt/index";
//重定向,地址变化,不共享数据,服务器根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址
return "redirect:/qt/login";
//普通的
return "qt/index";
(1)输入错误账号密码去登录,效果:
地址发送了变化
(2)输入正确的账号密码登录,效果:
地址发送了变化
主页也拿到了数据
ok了,感觉好像没什么问题了
这两个我也搞得乱乱的,不行在看吧
Day 22
今天有空就是写网页,好玩又很耗时间,而且审美也差哈哈
主要是先做了文章这块(中间)
好久没写了,写网页遇到几个问题:
1.高度自适应
2.内容超过后省略号表示
3.标签中li横向排列(我写的丑)
参考了https://blog.csdn.net/leewokan/article/details/6626774
至于文章流加载,用的是layui
Day 23
写网页遇到的问题
(2)横线不用hr吧,用这个
(3)p标签单行或多行超出显示省略号
// 单行显示省略号
p {
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
}
// 多行显示省略号,数字3为超出3行显示
//-webkit-line-clamp表示要展示的行数
p {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
今晚就写了两个东西
能折叠的公告栏
Day 29
谁看过我
要让图片在li中垂直居中
.f_two>ul{
width: 100%;
margin: 0 auto;
display: table;
}
.f_two>ul>li{
border: solid 1px black;
padding-top: 7px;
float: left;
width: 25%;
padding-bottom: 5px;
}
.f_two>ul>li>a>img{
margin: 0 auto;
display: block;
}
花了点时间,做了个很丑的网页
Day 02
写了个两个表,还不完整
article_info 文章表
字段名称 |
类型 |
是否主键 |
是否为空 |
字段描述 |
Id |
Bigint(30) |
Y |
N |
游记id |
userName |
varchar(20) |
N |
N |
用户昵称 |
userPhone |
varchar(20) |
N |
N |
用户手机号 |
userPicPath |
varchar(500) |
N |
N |
用户头像虚拟路径 |
articleTitle |
varchar() |
N |
N |
文章题目 |
articleContent |
MediumText |
N |
N |
文章内容 |
articlePublishDate |
Date |
N |
N |
文章发表日期 |
articleModifyDate |
Date |
N |
N |
文章修改日期 |
articleCommentSum |
Bigint(30) |
N |
N |
文章评论总数 |
articleType |
varchar(20) |
N |
N |
文章类型 |
articleRole |
varchar(20) |
N |
N |
文章权限 |
articleDateShow |
varchar(20) |
N |
N |
文章时间展示形式 |
articleType:1:普通文章;2:悬赏文章
articleRole:1:所有可见;2:VIP文章;3:私密文章
articleDateShow:1:今天;2:昨天;3:日期(年月-日)
dictionary_info 字典表
字段名称 |
类型 |
是否主键 |
是否为空 |
字段描述 |
Id |
int |
Y |
N |
字典id |
typeCode |
varchar(20) |
N |
N |
字典类型 |
valueId |
int |
N |
N |
属性id |
valueName |
varchar(30) |
N |
N |
属性名字 |
【typeCode】
articleType:1:普通文章;2:悬赏文章;
Day 05
今天把个人中心的页面写好了
Day 07
重新定义了一下文章表
文章权限:1:所有可见;2:VIP文章;3:私密文章
文章时间展示形式:1:今天;2:昨天;3:日期(年月-日):逻辑去判断
头像虚拟路径初始值:/statics/uploadfiles/tx.jpg
头像真实路径初始值:
F:\footprint\out\artifacts\GZLifeCircle_war_exploded\statics\uploadfiles\tx.jpg
文章评论总数:0
文章的悬赏金额:0
文章类型:1:普通文章;2:悬赏文章
文章的浏览量:0
文章权重:1
CREATE TABLE article_info(
id BIGINT(30) NOT NULL,
userName VARCHAR(20) NOT NULL,
userPhone VARCHAR(20) NOT NULL,
userPicPath VARCHAR(500) NOT NULL DEFAULT '/statics/uploadfiles/tx.jpg',
userLocPath VARCHAR(500) NOT NULL DEFAULT 'F:\\GZLifeCircle\\out\\artifacts\\GZLifeCircle_war_exploded\\statics\\uploadfiles\\tx.jpg',
articleTitle VARCHAR(20) NOT NULL,
articleContent MEDIUMTEXT NOT NULL,
articlePublishDate DATE NOT NULL,
articleModifyDate DATE NOT NULL,
articleCommentSum BIGINT(30) NOT NULL DEFAULT 0,
articleType INT NOT NULL,
articleRole INT NOT NULL,
articleDateShow INT NOT NULL,
articleOneSort INT NOT NULL,
articleTwoSort INT NOT NULL,
articleThreeSort INT NOT NULL,
articleWeight INT NOT NULL DEFAULT 1,
articleHits BIGINT(30) NOT NULL DEFAULT 0,
articleEnclosure VARCHAR(30),
articleReward BIGINT(30) NOT NULL DEFAULT 0,
PRIMARY KEY (id)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
Day 08
针对搜索功能,我又加了个样式
然后把代码贴上idea,基本资料也出来了
接下来是勋章和头像框,有空再写了
Day 12
发布文章要选分类
$(function(){
//动态加载一级分类列表
$.ajax({
type:"GET",//请求类型
url:"categorylevellist.json",//请求的url
data:{pid:null},//请求参数
dataType:"json",//ajax接口(请求url)返回的数据类型
success:function(data){//data:返回数据(json对象)
$("#categoryLevel1").html("");
var options = "";
for(var i = 0; i < data.length; i++){
options += "";
}
$("#categoryLevel1").html(options);
},
error:function(data){//当访问时候,404,500 等非200的错误状态码
alert("加载一级分类列表失败!");
}
});
//动态加载二级分类列表
$("#categoryLevel1").change(function(){
var categoryLevel1 = $("#categoryLevel1").val();
if(categoryLevel1 != '' && categoryLevel1 != null){
$.ajax({
type:"GET",//请求类型
url:"categorylevellist.json",//请求的url
data:{pid:categoryLevel1},//请求参数
dataType:"json",//ajax接口(请求url)返回的数据类型
success:function(data){//data:返回数据(json对象)
$("#categoryLevel2").html("");
var options = "";
for(var i = 0; i < data.length; i++){
options += "";
}
$("#categoryLevel2").html(options);
},
error:function(data){//当访问时候,404,500 等非200的错误状态码
alert("加载二级分类失败!");
}
});
}else{
$("#categoryLevel2").html("");
var options = "";
$("#categoryLevel2").html(options);
}
$("#categoryLevel3").html("");
var options = "";
$("#categoryLevel3").html(options);
});
//动态加载三级分类列表
$("#categoryLevel2").change(function(){
var categoryLevel2 = $("#categoryLevel2").val();
if(categoryLevel2 != '' && categoryLevel2 != null){
$.ajax({
type:"GET",//请求类型
url:"categorylevellist.json",//请求的url
data:{pid:categoryLevel2},//请求参数
dataType:"json",//ajax接口(请求url)返回的数据类型
success:function(data){//data:返回数据(json对象)
$("#categoryLevel3").html("");
var options = "";
for(var i = 0; i < data.length; i++){
options += "";
}
$("#categoryLevel3").html(options);
},
error:function(data){//当访问时候,404,500 等非200的错误状态码
alert("加载三级分类失败!");
}
});
}else{
$("#categoryLevel3").html("");
var options = "";
$("#categoryLevel3").html(options);
}
});
});
Day 1/20
发文章,使用的是summernote编辑器
//文章图片上传
@RequestMapping(value = "uploadEditorImg", method = {RequestMethod.POST, RequestMethod.GET})
@ResponseBody
public Object uploadEditorImg(HttpSession session, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 转换request,解析出request中的文件
MultipartHttpServletRequest mr = (MultipartHttpServletRequest) request;
Map map = new HashMap<>();
// 返回此请求中包含的多部分文件的参数名的字符串对象的迭代器。这些是表单的字段名(name属性)(与普通参数类似),而不是原始文件名
Iterator iterator = mr.getFileNames();
String fileName = "";
// 判断是否存在下一个元素
while (iterator.hasNext()) {
// 返回此请求中上载文件的内容和说明,如果不存在则返回空值;next:指针下移,返回该指针所指向的元素
MultipartFile multipartFile = mr.getFile(iterator.next());
String realPath = session.getServletContext().getRealPath("/statics/TextImg/");
// 获取原始文件名字,包括后缀
String originalFilename = multipartFile.getOriginalFilename();
// 获得文件名的扩展名,不包括.
String suffix = FilenameUtils.getExtension(originalFilename);
String base = "${pageContext.request.contextPath }/";
// equalsIgnoreCase:忽略大小写
if (suffix.equalsIgnoreCase("jpg") || suffix.equalsIgnoreCase("png")
|| suffix.equalsIgnoreCase("jpeg") || suffix.equalsIgnoreCase("pneg")) {
fileName = System.currentTimeMillis() + new Random().nextInt(1000000) + "_wz.jpg";//新的文件名
// 路径是directory,名字是filename
File targetFile = new File(realPath, fileName);//新建文件夹
// 判断路径(文件夹)是否存在,如果不存在就创建一个
if (!targetFile.getParentFile().exists()) {
System.out.println("文件夹不存在:"+targetFile.getParentFile());
// getParentFile()的作用是获得父目录
targetFile.getParentFile().mkdirs();
}
System.out.println("文件夹存在:"+targetFile.getParentFile());
try {
// 将上传文件存储到服务器中
//使用此方法保存必须要绝对路径且文件夹必须已存在,否则报错
multipartFile.transferTo(targetFile);
} catch (Exception e) {
e.printStackTrace();
}
}
map.put("Timg", "../../statics/TextImg/" + fileName);
}
return JSONObject.toJSONString(map);
}
也可以插入视频,但是暂时只有这些
Day 1/23
改了一下,修改日期可以为空
然后把昵称,头像从文章表删除,只通过手机号去查用户表,这样就不会因为修改了用户表导致还要重新更新全部文章,这样不合理,删掉后也符合三大范式的规则。
然后发现网页的附件没有写样式,暂时不做
文章发表做好了
内容就以HTML的形式存储在数据库了
看了下数据库,发现Date类型的数据我怎么转都没有时分秒,不知道什么原因,就改了类型
//new日期对象
Date date = new Date();
//转换提日期输出格式
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
articleInfo.setArticlePublishDate(dateFormat.format(date));
这样就行了
然后再把二级和三级分类改为可以为空,就完成文章发布这个模块了。
Day 1/30
“躺尸”N天,不敢出门,忽然醒悟,打开电脑好好学习
今天先做文章的流加载
然后运行时候出现
调式了一下,发现数据是有的,就是在最后显示出现了问题
- (33019 ms) - 2020-1-30 13:20:11[DEBUG](AbstractMessageConverterMethodProcessor.java:185) Written [{"articleInfos":[{"articleCommentSum":0,"articleContent":"内容
","articleDateShow":1,"articleHits":0,"articleOneSort":1,"articlePublishDate":"2020-01-23 00:00:00.0","articleReward":0,"articleRole":1,"articleThreeSort":11,"articleTitle":"标题","articleTwoSort":4,"articleType":2,"articleWeight":1,"id":1,"userPhone":"17820423525"},{"articleCommentSum":0,"articleContent":"
","articleDateShow":1,"articleHits":0,"articleOneSort":1,"articlePublishDate":"2020-01-23 00:00:00.0","articleReward":0,"articleRole":1,"articleThreeSort":11,"articleTitle":"1","articleTwoSort":4,"articleType":2,"articleWeight":1,"id":2,"userPhone":"17820423525"},{"articleCommentSum":0,"articleContent":"这是文章的内容
123456789AZaz
/.,\\';][
","articleDateShow":1,"articleHits":0,"articleOneSort":1,"articlePublishDate":"2020-01-24 00:00:00.0","articleReward":0,"articleRole":1,"articleThreeSort":11,"articleTitle":"标题","articleTwoSort":4,"articleType":1,"articleWeight":1,"id":3,"userPhone":"17820423525"},{"articleCommentSum":0,"articleContent":"AA
","articleDateShow":1,"articleHits":0,"articleOneSort":1,"articlePublishDate":"2020-01-24 00:00:00.0","articleReward":0,"articleRole":1,"articleThreeSort":12,"articleTitle":"标题","articleTwoSort":4,"articleType":1,"articleWeight":1,"id":4,"userPhone":"17820423525"},{"articleCommentSum":0,"articleContent":"订单
","articleDateShow":1,"articleHits":0,"articleOneSort":1,"articlePublishDate":"2020-01-24 14:01:15.0","articleReward":0,"articleRole":1,"articleThreeSort":11,"articleTitle":"的","articleTwoSort":4,"articleType":2,"articleWeight":1,"id":5,"userPhone":"17820423525"}]}] as "application/json;charset=UTF-8" using [org.springframework.http.converter.StringHttpMessageConverter@3474a243]
- (33019 ms) - 2020-1-30 13:20:11[DEBUG](DispatcherServlet.java:1034) Null ModelAndView returned to DispatcherServlet with name 'spring': assuming HandlerAdapter completed request handling
- (33019 ms) - 2020-1-30 13:20:11[DEBUG](FrameworkServlet.java:1000) Successfully completed request
后面发现
//这样写是错误的
'item.articleTitle'+
//这样也是错误的
'+item.articleTitle+'+
//这样才是对的
''+item.articleTitle+''+
点击加载更多,会刷新下一页的数据,没有数据就会这样显示
原来想法是不用点,鼠标滚动更新,还有文章内容前面是有多个空格的,不知道为什么显示不了,后续再看看
接下来是阅读全文,点击后能进入文章的详情,以及看到文章的评论,回复等内容
静态页面写好了
接下来就是评论和回复的功能了
写数据表的时候忽然想起,不能用Integer,要用long,于是全改了
commentReply_info 评论回复表
字段名称 |
类型 |
是否主键 |
是否为空 |
字段描述 |
commentId |
bigint(30) |
Y |
N |
评论id |
id |
bigint(30) |
N |
N |
文章id |
replyId |
bigint(30) |
N |
N |
回复id |
commentContent |
varchar(200) |
N |
N |
评论的内容 |
commentTime |
varchar(25) |
N |
N |
评论时间 |
commentUserName |
varchar(20) |
N |
N |
评论用户的昵称 |
commentUserPhone |
varchar(20) |
N |
N |
评论用户的手机号 |
commentUserPicPath |
varchar(500) |
N |
N |
评论用户的头像 |
replyUserPhone |
varchar(20) |
N |
N |
回复用户的手机号 |
replyUserName |
varchar(20) |
N |
N |
回复用户的昵称 |
commentFabulous |
bigint(30) |
N |
N |
评论的点赞数量 |
commentFlag |
enum |
N |
N |
评论的标识 |
回复id:默认是0,代表是主评论
回复id:拿的是一级评论的id
回复人:是从一级评论拿的commentUserName(手机号也一样)
评论时间:默认是0000-00-00 00:00:00
评论用户的头像:默认是/statics/uploadfiles/tx.jpg
回复用户的手机号:默认是空(当是主评论的时候没有回复者手机号)
回复用户的昵称:默认是空(当是主评论的时候没有回复者昵称)
评论的点赞数量:默认是0
评论的标识:枚举类型(0,1),默认是0
评论的标识:如果评论有点赞,控制层会将这个评论的标识从0改为1,但是不会去改数据库
CREATE TABLE commentReply_info(
commentId BIGINT(30) NOT NULL,
id BIGINT(30) NOT NULL,
replyId BIGINT(30) NOT NULL DEFAULT '0',
commentContent VARCHAR(200) NOT NULL,
commentTime VARCHAR(25) DEFAULT '0000-00-00 00:00:00',
commentUserName VARCHAR(20) NOT NULL,
commentUserPhone VARCHAR(20) NOT NULL,
commentUserPicPath VARCHAR(500) DEFAULT '/statics/uploadfiles/tx.jpg',
replyUserPhone VARCHAR(20) DEFAULT NULL,
replyUserName VARCHAR(20) DEFAULT NULL,
commentFabulous BIGINT(30) NOT NULL DEFAULT '0',
commentFlag ENUM('0','1') NOT NULL DEFAULT '0',
PRIMARY KEY (commentId)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
commentFabulous_info 评论点赞表
字段名称 |
类型 |
是否主键 |
是否为空 |
字段描述 |
Id |
bigint(30) |
Y |
N |
id |
commentId |
bigint(30) |
N |
N |
文章评论的id |
userPhone |
varchar(20) |
N |
N |
用户手机号 |
CREATE TABLE commentFabulous_info(
id BIGINT(30) NOT NULL AUTO_INCREMENT,
commentId BIGINT(30) NOT NULL,
userPhone VARCHAR(20) NOT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
代码写好后,没数据的样子
数据库先插两条数据,效果:
在做文章二级回复的时候出现了一个bug
HandlerMethod details:
Controller [cn.controller.qt.ArticleController]
Method [public java.lang.Object cn.controller.qt.ArticleController.getUserReply(java.lang.String,java.lang.String,java.lang.String,long,javax.servlet.http.HttpSession)]
org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'replyId' is not present
@RequestParam(value = "replyId") String replyId,
我是这样写的,看报错是没有数据过来,去页面改一下就行了,字段写错了
写完后简单测试了一下,发现一级评论可以正确发布,但是回复(二级评论)会报错(空指针异常),但是我如果是先发了一级评论,然后刷新,再回复, 是没问题的,同时偶尔出现一个问题,就是回复发布后,显示两条同样的回复,但是我刷新后又能正确显示了。
经过一系列的调试,发现了问题,就是我在对回复进行回复的时候,会发现,我打印出来的id和这条回复在数据库的id不对,我拿了一个不存在的id去查肯定报空指针,但是我在刷新后,点哪个回复,居然发现打印正确,所以刷新后就能正确回复,但是这样明显不合理,代码如下:
//@人的回复评论(二级评论)
@RequestMapping(value = "getUserReply")
@ResponseBody
public Object getUserReply(@RequestParam(value = "commentContent") String commentContent,//评论内容
@RequestParam(value = "commentTime") String commentTime,//评论时间
@RequestParam(value = "replyId") String replyId, //一级评论id
@RequestParam(value = "id") long id,//文章id
HttpSession session) {
UserInfo userInfo = (UserInfo) session.getAttribute(Constants.QT_USER_SESSION);
// 一级评论的id
long rid = Long.parseLong(replyId);
// 由数据中心 ID 和机器 ID 作区分
IdGenerator idWorker = new IdGenerator(0, 0);
// 回复一级评论的id
long commentId = idWorker.nextId();
CommentReplyInfo commentReplyInfo1 = new CommentReplyInfo();
Map map = new HashMap<>();
if(session.getAttribute(Constants.QT_USER_SESSION)==null||session.getAttribute(Constants.QT_USER_SESSION)==""){
map.put("flag",false);
}else {
try {
// 查询要回复的那个一级评论的手机号和用户昵称
commentReplyInfo1 = articleInfoService.getCommentReplyInfo1(rid);
} catch (Exception e) {
e.printStackTrace();
}
CommentReplyInfo commentReplyInfo = new CommentReplyInfo(commentId,id,rid,commentContent,commentTime,userInfo.getUserName(),userInfo.getUserPhone(),userInfo.getUserPicPath(),commentReplyInfo1.getCommentUserPhone(),commentReplyInfo1.getCommentUserName(),0,"0");
boolean flag = false;
try {
flag = articleInfoService.addCommentReplyInfo2(commentReplyInfo);
} catch (Exception e) {
e.printStackTrace();
}
map.put("flag",true);
map.put("commentReplyInfo", commentReplyInfo);
}
return JSONObject.toJSONString(map);
}
后面我发现我应该是用了这个作为评论的id的原因(IdGenerator类),具体是什么原因我不知道,后续再看看,暂时使用这个
long commentId = System.currentTimeMillis();(这个不合理,先暂时用着)
package cn.util;
/**
* 用于生成唯一 ID
* 关于如何在系统中生成唯一性 ID 的问题(如订单号、批次号等),一直困扰了许久。因为还要考虑并发的问题,所以时间戳 + 随机数的组合并不可取,Java 中的 UUID 是一种可取的方法,但它的缺点是序列号太长了,而且没有可读性,对用户来说这么一堆乱码是极不友好的。
推特的工程师 snowflake 也提出了一个在分布式系统中生成唯一序列的方法。SnowFlake 的优点是,效率高,整体上按照时间自增排序,提高了序列号的可读性,对用户来说也比较友好,并且整个分布式系统内不会产生 ID 碰撞(由数据中心 ID 和机器 ID 作区分)。
2.SnowFlake 算法的 Java 实现
* Twitter_Snowflake
* SnowFlake 的结构如下(每部分用-分开):
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
* 1 位标识,由于 long 基本类型在 Java 中是带符号的,最高位是符号位,正数是 0,负数是 1,所以 id 一般是正数,最高位是 0
* 41 位时间戳(毫秒级),注意,41 位时间戳不是存储当前时间的时间戳,而是存储时间戳的差值(当前时间戳 - 开始时间戳)
* 得到的值),这里的的开始时间戳,一般是我们的 id 生成器开始使用的时间,由我们程序来指定的(如下面程序 SnowflakeIdWorker 类的 startTime 属性)。41 位的时间戳,可以使用 69 年,年 T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
* 10 位的数据机器位,可以部署在 1024 个节点,包括 5 位 datacenterId 和 5 位 workerId
* 12 位序列,毫秒内的计数,12 位的计数顺序号支持每个节点每毫秒(同一机器,同一时间戳)产生 4096 个 ID 序号
* 加起来刚好 64 位,为一个 Long 型。
*/
public class IdGenerator {
/* 开始时间戳 (2018-09-01) */
private final long twepoch = 1535731200L;
/**
* 机器id所占的位数
*/
private final long workerIdBits = 5L;
/**
* 数据标识id所占的位数
*/
private final long datacenterIdBits = 5L;
/**
* 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
*/
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
/**
* 支持的最大数据标识id,结果是31
*/
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
/**
* 序列在id中占的位数
*/
private final long sequenceBits = 12L;
/**
* 机器ID向左移12位
*/
private final long workerIdShift = sequenceBits;
/**
* 数据标识id向左移17位(12+5)
*/
private final long datacenterIdShift = sequenceBits + workerIdBits;
/**
* 时间戳向左移22位(5+5+12)
*/
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
/**
* 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
*/
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
/**
* 工作机器ID(0~31)
*/
private long workerId;
/**
* 数据中心ID(0~31)
*/
private long datacenterId;
/**
* 毫秒内序列(0~4095)
*/
private long sequence = 0L;
/**
* 上次生成ID的时间戳
*/
private long lastTimestamp = -1L;
//==============================Constructors=====================================
/**
* 构造函数
*
* @param workerId 工作ID (0~31)
* @param datacenterId 数据中心ID (0~31)
*/
public IdGenerator(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
// ==============================Methods==========================================
/**
* 获得下一个ID (该方法是线程安全的)
*
* @return SnowflakeId
*/
public synchronized long nextId() {
long timestamp = timeGen();
//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
if (timestamp < lastTimestamp) {
throw new RuntimeException(
String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
//如果是同一时间生成的,则进行毫秒内序列
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
//毫秒内序列溢出
if (sequence == 0) {
//阻塞到下一个毫秒,获得新的时间戳
timestamp = tilNextMillis(lastTimestamp);
}
}
//时间戳改变,毫秒内序列重置
else {
sequence = 0L;
}
//上次生成ID的时间戳
lastTimestamp = timestamp;
//移位并通过或运算拼到一起组成64位的ID
return ((timestamp - twepoch) << timestampLeftShift) //
| (datacenterId << datacenterIdShift) //
| (workerId << workerIdShift) //
| sequence;
}
/**
* 阻塞到下一个毫秒,直到获得新的时间戳
*
* @param lastTimestamp 上次生成ID的时间戳
* @return 当前时间戳
*/
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
/**
* 返回以毫秒为单位的当前时间
*
* @return 当前时间(毫秒)
*/
protected long timeGen() {
return System.currentTimeMillis();
}
/**
* 测试
*/
// public static void main(String[] args) {
// IdGenerator idWorker = new IdGenerator(0, 0);
// long id = idWorker.nextId();
// System.out.println(id);
// }
}
接下来是评论的删除功能
写完后发现当我删除一级评论时候,回复的回复删除不了;当我删除回复的时候,回复的回复也删除不了
在页面效果没问题,但是在数据库有问题,后续再看
//删除评论
@RequestMapping(value = "deleteCommentById")
@ResponseBody
public Object deleteCommentById(@RequestParam(value = "commentId") String commentId,
HttpSession session) {
long cId = Long.parseLong(commentId);
boolean flag = false;
// 通过文章id查询评论的信息
CommentReplyInfo commentReplyInfo = null;
try {
commentReplyInfo = articleInfoService.getCommentReplyInfo1(cId);
} catch (Exception e) {
e.printStackTrace();
}
// 等于0删除整块(一级和二级),否则就删除回复的评论(包括再回复),或者是再回复的评论
if (commentReplyInfo.getReplyId()==0){
try {
// 删除文章一级评论
flag = articleInfoService.deleteCommentById1(cId);
} catch (Exception e) {
e.printStackTrace();
}
try {
// 删除这条评论的全部点赞(在这里是删除一级评论的全部点赞)
flag = articleInfoService.deleteCommentFabulousByCommentId(cId);
} catch (Exception e) {
e.printStackTrace();
}
try {
// 暂时
// 拿到一级评论的文章id,对这篇文章的评论总数减1
flag = articleInfoService.updateArticleCommentSum1(commentReplyInfo.getId());
} catch (Exception e) {
e.printStackTrace();
}
try {
// 拿到一级评论的id,然后作为回复id找到这条一级评论的全部回复,然后删除
// 问题是:回复的回复删除不了
flag =articleInfoService.deleteCommentByReplyId(commentReplyInfo.getCommentId());
} catch (Exception e) {
e.printStackTrace();
}
}else {
try {
// 不是0说明是回复,直接根据拿到的评论id删除这个评论就行了
flag = articleInfoService.deleteCommentById1(cId);
} catch (Exception e) {
e.printStackTrace();
}
try {
// 删除回复的全部点赞
flag = articleInfoService.deleteCommentFabulousByCommentId(cId);
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
接下来是评论的点赞,取消点赞
至此,基本就出来了,其中一些bug后续有空再回来解决了
Day 2/8
回复功能:点击回复获取的这条评论的id是错误的
// long commentId = System.currentTimeMillis();
我使用上面的方式去写id,功能就没有问题,但是不合理,因为可能会出现两个人在同一个时间评论,这样id就冲突了
于是我找了个能生成uuid的类:https://blog.csdn.net/rhx_1989/article/details/82784991
但是出现了像前面一系列的问题
测试了很久,终于找到了回复那一系列的问题原因,因为我使用的long类型,数字太长了,在js获取丢失了精度
解决方法:https://blog.csdn.net/weixin_42717928/article/details/104256759
控制层这样就行了
// 由数据中心 ID 和机器 ID 作区分
IdGenerator idWorker = new IdGenerator(0, 0);
String commentId = String.valueOf(idWorker.nextId());
然后接下来把主页的头像区分一下,没有登录就进入主页的为游客,显示游客头像,登录过的人显示该用户头像
然后退出登录加个弹窗二次提示
退出登录
直接加个判断是否显示管理员入口
然后是个人资料页面
Day 2/9
主页加些判断,第一部分就做好了
然后第二部分
这里显示我关注的用户当天发的文章(最新的),最大限制数10条
待定:想实现类似这样的功能的,已读的不显示
临时加了个表
follow_info 关注表
字段名称 |
类型 |
是否主键 |
是否为空 |
字段描述 |
Id |
varchar(30) |
Y |
N |
id |
userPhone |
varchar(20) |
N |
N |
用户手机号 |
followUserPhone |
varchar(20) |
N |
N |
关注的那个用户的手机号 |
followTime |
Datetime |
N |
N |
关注时间 |
CREATE TABLE follow_info(
id VARCHAR(30) NOT NULL,
userPhone VARCHAR(20) NOT NULL,
followUserPhone VARCHAR(20) NOT NULL,
followTime DATETIME NOT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
要根据一堆关注用户去查他们的文章,可以使用批量查询
//关注的用户
//这里使用String的原因是,这样我得到的对象可以放在List定义的参数里面
List followInfos = null;
//关注用户的文章
List articleInfos = null;
//报了一个bug
org.apache.ibatis.binding.BindingException: Parameter 'list' not found. Available parameters are [endStr, startStr, userPhone, param3, param1, param2]
部分代码如下:
// GregorianCalendar是Calendar的子类
Calendar calendar = new GregorianCalendar();
// -1是一天前,1是后一天
// 指示一个月中的某天
calendar.add(Calendar.DAY_OF_MONTH,0);
//一天的开始时间 yyyy:MM:dd 00:00:00
// 指示一天中的小时
calendar.set(Calendar.HOUR_OF_DAY,0);
// 指示一小时中的分钟
calendar.set(Calendar.MINUTE,0);
// 指示一分钟中的秒
calendar.set(Calendar.SECOND,0);
// 指示一秒中的毫秒
calendar.set(Calendar.MILLISECOND,0);
// 得到的是这种类型的日期:Tue Feb 11 00:00:00 CST 2020
Date dayStart = calendar.getTime();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String startStr = simpleDateFormat.format(dayStart);
//一天的结束时间 yyyy:MM:dd 23:59:59
calendar.set(Calendar.HOUR_OF_DAY,23);
calendar.set(Calendar.MINUTE,59);
calendar.set(Calendar.SECOND,59);
// 1秒=1000毫秒
calendar.set(Calendar.MILLISECOND,999);
Date dayEnd = calendar.getTime();
String endStr = simpleDateFormat.format(dayEnd);
articleInfos = qtUserInfoService.batchGetFollowingsArticleInfos(startStr,endStr,followInfos);
public List batchGetFollowingsArticleInfos(String startStr,String endStr,List userPhones) throws Exception;
@Override
public List batchGetFollowingsArticleInfos(String startStr,String endStr,List userPhones) throws Exception{
return qtUserInfoMapper.batchGetFollowingsArticleInfos(startStr,endStr,userPhones);
}
public List batchGetFollowingsArticleInfos(@Param(value = "startStr") String startStr,@Param(value = "endStr") String endStr,@Param(value = "userPhone") List userPhones) throws Exception;
经过调试我发现如果只是一个参数,刚刚开始我是只有List
但是我加入另外两个参数,程序就出错了,后面改为collection="userPhone",问题解决
接下来,当用户关注的人当天没有发文章,会有一个默认的文案
原来是这样写的
request.setAttribute("articleInfosError","亲,关注的朋友今天没有发文章哦");
然后一堆乱码,不知道什么原因,试了很多解决方法都不行
最后只能这样写了
String articleInfosError = "亲,关注的朋友今天没有发文章哦";
request.setAttribute("articleInfosError",articleInfosError);
随便插了些数据,这部分就做好了
然后第三部分:热门悬赏榜单
我自己定了些规则
热度悬赏榜单:
规则1:前提是悬赏文章
规则2:每2小时更新一次,待定
规则3:
(1)金额1-50(+1)51-100(+2)101-500(+3)501-1000(+4)
(2)浏览量100-500(+1)501-10000(+2),10001-100000(+3),100001>(+4)
(3)文章权重:1>
(4)互动量(评论数量):0-500(+1)501-10000(+2),10001-100000(+3),100001>(+4)
(5)回答质量:至少有一个评论500赞以上
规则4:
加一个字段是0,后台可以调整为1,则不显示在热度榜单,以达到管控
文章权重:文章都是1,这个是后台进行管控(买热度可以加权重)
实现也是用了很笨的方法
功能是实现了,但是代码写的很烂。。。
接下来是第四部分:全部文章内容的显示
然后写完了发现了个bug,文章,视频或者图片过多直接顶下去了,本来的想法是只能显示这么多,多余的省略
.flow_top_center_center{
font-size: 18px;
overflow: hidden;
line-height:25px;
width: 100%;
height:80%;
}
这就可以把超过div的内容隐藏掉,效果成下面图片那样
.flow_top_center_center>a{
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 9;
overflow: hidden;
height: auto;
cursor: pointer;
}
//这样的话这部分的样式好像就部分实现不了了,后续再看看
还有这样就不是很好看,预期是多余的空白能挤压掉,后续再看看
还有这个本想做一个判断的,不知道为什么拿不到值,于是普通文章也这样显示了,只不过悬赏金额为0
然后我把文章的这个字段改为了varchar类型,作用是判断文章是以今天,昨天,还是日期来显示
主要是通过这个类:参考:https://www.xuebuyuan.com/2006093.html
package cn.util;
/**
* Created by 漫步云端 on 2020/2/13.
*/
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
public class CalendarTest {
/**
* @param args
*/
// public static void main(String[] args) {
//
String time = formatDateTime("2020-02-13 15:41:15");
System.out.println("time:"+time);
time = formatDateTime("2020-02-12 15:45");
System.out.println("time:"+time);
time = formatDateTime("2013-08-11 15:43");
System.out.println("time:"+time);
// }
/**
* 格式化时间
* @param time
* @return
*/
public String formatDateTime(String time) {
SimpleDateFormat format = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm");
System.out.println("format"+format);
if(time==null ||"".equals(time)){
return "";
}
Date date = null;
try {
// parse表示将字符串转化为时间:Thu Feb 13 15:41:00 CST 2020
date = format.parse(time);
} catch (ParseException e) {
e.printStackTrace();
}
// 现在的日期
Calendar current = Calendar.getInstance();
Calendar today = Calendar.getInstance(); //今天
today.set(Calendar.YEAR, current.get(Calendar.YEAR));
today.set(Calendar.MONTH, current.get(Calendar.MONTH));
today.set(Calendar.DAY_OF_MONTH,current.get(Calendar.DAY_OF_MONTH));
// Calendar.HOUR——12小时制的小时数 Calendar.HOUR_OF_DAY——24小时制的小时数
today.set( Calendar.HOUR_OF_DAY, 0);
today.set( Calendar.MINUTE, 0);
today.set(Calendar.SECOND, 0);
Calendar yesterday = Calendar.getInstance(); //昨天
yesterday.set(Calendar.YEAR, current.get(Calendar.YEAR));
yesterday.set(Calendar.MONTH, current.get(Calendar.MONTH));
yesterday.set(Calendar.DAY_OF_MONTH,current.get(Calendar.DAY_OF_MONTH)-1);
yesterday.set( Calendar.HOUR_OF_DAY, 0);
yesterday.set( Calendar.MINUTE, 0);
yesterday.set(Calendar.SECOND, 0);
// setTime()方法以毫秒设置Date对象
current.setTime(date);
// 这里有个问题,超过今天都是今天,不过因为文章的发表不会超过当天,所以这里使用没问题
if(current.after(today)){
// 以空格分隔时间,取索引为1的时间
return "今天 "+time.split(" ")[1];
}else if(current.before(today) && current.after(yesterday)){
return "昨天 "+time.split(" ")[1];
}else{
// 返回指定字符在字符串中第一次出现处的索引,这里是4
int index = time.indexOf("-")+1;
// 截取除了年的其他部分
return time.substring(index, time.length());
}
}
}
也不用插入数据库,仅仅是一个过渡来显示而已
//用来判断文章是否以今天,昨天还是日期来显示
CalendarTest calendarTest = new CalendarTest();
articleInfo1.setArticleDateShow(calendarTest.formatDateTime(articleInfo1.getArticlePublishDate()));
效果就这样
点击文章内容可以进入文章的详情
''+item.articleContent+''+
在这里的话我想点了别人的文章进入详情,看的是别人的个人空间,而不是自己的,于是我加了两个参数,文章手机号和登录用户的手机号,如果一致,说明这个文章是这个用户发的,就进入该用户的个人中心,如果不一致,说明此文章不是该用户发的,就进入此文章作者的个人中心。
因为同时也是进入了这篇文章的详情,浏览量要+1,但是这个要怎么实现呢,感觉有难度。。。
百度看到一种简单的,放入session中的缺点是如果关闭浏览器再次打开该文章,还是会+1. 总比每次加1要好
//可以在做一个标识放到session中,例如session.setAttribute("Flag","true")
String flag = (String)session.getAttribue("Flag");
if(null!=flag&&"true".equals(flag)){
break;
}else{
//你更新的操作
session.setAttribute("Flag","true")
}
//这样好像可以放一个map对象(用户手机号,文章id)
但是不是我想要的,这个浏览量先暂时不做,后续再看看
接下来是点击了自己的文章进入自己的个人中心,文章详情是这样
然后回到这里,我发了三条评论,显示没问题,但是一刷新第三条评论显示不了了
数据库数据插入成功,控制层也拿到数据,测试后发现三级以后的回复都显示不了了,心累
控制台一堆乱码,解决方法:https://blog.csdn.net/m0_37893932/article/details/78280663(我用了第二个)
经过调试后发现初始的显示状态只能显示一级和二级评论,三级以后的显示不了,但是直接评论能显示,只要不刷新
然后发现评论总数也有问题,假如一级评论下有二级评论2条,那我删除一级评论,评论总数只是减一,应该是减3
这个bug有点难搞,后续再调
接下里是热门文章,就只显示浏览量最高的五篇文章而已
然后这几部分暂时不做
Day 20
resource_info 资源表
字段名称 |
类型 |
是否主键 |
是否为空 |
字段描述 |
Id |
bigInt(30) |
Y |
N |
id |
resourceName |
varchar(30) |
N |
N |
资源名字 |
resourcePicPath |
varchar(500) |
N |
N |
资源图片虚拟路径 |
resourceDetails |
varchar(500) |
N |
N |
资源介绍 |
resourceValue |
int |
N |
N |
资源的价格 |
resourceUserName |
Varchar(20) |
N |
N |
资源上传者昵称 |
resourcePublishDate |
datetime |
N |
N |
资源的发布时间 |
resourceDownloadLink |
Varchar(500) |
N |
N |
资源的下载链接(虚拟路径) |
resourceLocPath |
Varchar(500) |
N |
N |
资源的真实下载路径 |
resourceStatus |
int |
N |
N |
资源的状态 |
resourceUserPhone |
Varchar(20) |
N |
N |
资源上传者手机号 |
resourceOneSort |
int |
N |
N |
资源的一级分类 |
resourceTwoSort |
int |
N |
Y |
资源的二级分类 |
resourceThreeSort |
int |
N |
Y |
资源的三级分类 |
resourceDownloadCount |
bigInt(30) |
N |
N |
资源的下载量(以第一次为准) |
资源的状态:1:待审核;2:审核不通过;3:审核通过;
资源下载量:默认是0
CREATE TABLE resource_info(
id BIGINT(30) NOT NULL AUTO_INCREMENT,
resourceName VARCHAR(30) NOT NULL,
resourcePicPath VARCHAR(500) NOT NULL,
resourceDetails VARCHAR(500) NOT NULL,
resourceValue INT NOT NULL,
resourceUserName VARCHAR(20) NOT NULL,
resourcePublishDate DATETIME NOT NULL,
resourceDownloadLink VARCHAR(500) NOT NULL,
resourceLocPath VARCHAR(500) NOT NULL,
resourceStatus INT NOT NULL,
resourceUserPhone VARCHAR(20) NOT NULL,
resourceOneSort INT NOT NULL,
resourceTwoSort INT NOT NULL,
resourceThreeSort INT NOT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
写了个很丑的页面
- (87831 ms) - 2020-2-20 23:02:26[DEBUG](InvocableHandlerMethod.java:168) Error resolving argument [0] [type=cn.pojo.ResourceDownload]
HandlerMethod details:
Controller [cn.controller.qt.ResourceDownloadController]
Method [public java.lang.String cn.controller.qt.ResourceDownloadController.UploadResources(cn.pojo.ResourceDownload,javax.servlet.http.HttpSession,javax.servlet.http.HttpServletRequest,org.springframework.web.multipart.MultipartFile)]
org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'resourceDownload' on field 'resourceDownloadLink': rejected value [org.springframework.web.multipart.commons.CommonsMultipartFile@2dbf939f]; codes [typeMismatch.resourceDownload.resourceDownloadLink,typeMismatch.resourceDownloadLink,typeMismatch.java.lang.String,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [resourceDownload.resourceDownloadLink,resourceDownloadLink]; arguments []; default message [resourceDownloadLink]]; default message [Failed to convert property value of type 'org.springframework.web.multipart.commons.CommonsMultipartFile' to required type 'java.lang.String' for property 'resourceDownloadLink'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [org.springframework.web.multipart.commons.CommonsMultipartFile] to required type [java.lang.String] for property 'resourceDownloadLink': no matching editors or conversion strategy found]
报了个bug,把name的名字改一下就行了
我的二级和三级分类是允许为空的,于是我上传资源三级分类为空,报了个bug
- (46630 ms) - 2020-2-20 23:47:59[DEBUG](InvocableHandlerMethod.java:168) Error resolving argument [0] [type=cn.pojo.ResourceDownload]
HandlerMethod details:
Controller [cn.controller.qt.ResourceDownloadController]
Method [public java.lang.String cn.controller.qt.ResourceDownloadController.UploadResources(cn.pojo.ResourceDownload,javax.servlet.http.HttpSession,javax.servlet.http.HttpServletRequest,org.springframework.web.multipart.MultipartFile)]
org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'resourceDownload' on field 'resourceThreeSort': rejected value []; codes [typeMismatch.resourceDownload.resourceThreeSort,typeMismatch.resourceThreeSort,typeMismatch.int,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [resourceDownload.resourceThreeSort,resourceThreeSort]; arguments []; default message [resourceThreeSort]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'int' for property 'resourceThreeSort'; nested exception is java.lang.NumberFormatException: For input string: ""]
百度了一下:
出现Field error in object ‘xxx’ on field ‘XXX’: rejected value [];错误
在ssm框架中,前端没有没有传值或者搜索条件为空时,传值时无法转换为int,会导致加载controller之前就报错,故应把属性值得类型改为Integer,即可接收到null。
于是我查看了我的pojo的类,发现我定义的是int,哎,短路了,改为Integer就行了
上传资源的代码
@RequestMapping(value="/UploadResources",method= RequestMethod.POST)
public String UploadResources(ResourceDownload resourceDownload, HttpSession session, HttpServletRequest request,
@RequestParam(value="a_downloadLink",required= false) MultipartFile attach ){
String downloadLink = null; //下载链接
String resourceLocPath = null; //资源的服务器存储路径
String resourceFileName = null; //上传的资源文件名称
String resourcePicPath = null; //资源图片的虚拟路径
if (session.getAttribute(Constants.QT_USER_SESSION)==null||session.getAttribute(Constants.QT_USER_SESSION)==""){
// 没有登录跳到登录页面
return "redirect:/qt/login";
}
if(!attach.isEmpty()){
String path = request.getSession().getServletContext().getRealPath("statics"+ File.separator+"uploadfiles");
logger.info("uploadFile path: " + path);
//获取原始文件名字,包括后缀
String oldFileName = attach.getOriginalFilename();
//获得文件名的扩展名,不包括.
String prefix = FilenameUtils.getExtension(oldFileName);
//equalsIgnoreCase:忽略大小写
if(prefix.equalsIgnoreCase("doc")||prefix.equalsIgnoreCase("docx")||prefix.equalsIgnoreCase("pdf")||prefix.equalsIgnoreCase("rar")||prefix.equalsIgnoreCase("zip")||prefix.equalsIgnoreCase("exe")||prefix.equalsIgnoreCase("ppt")||prefix.equalsIgnoreCase("txt")||prefix.equalsIgnoreCase("xlsx")){
String resourceName = null;
// 资源展示的扩展名图片
resourcePicPath = "../../statics/img/" + prefix+".jpg";
try {
//资源文件名字
resourceName = resourceDownload.getResourceName();
} catch (Exception e) {
e.printStackTrace();
}
// 资源名字:上传时输入的资源名字.文件后缀
resourceFileName = resourceName + "."+prefix;
File targetFile = new File(path,resourceFileName);
//判断路径(文件夹)是否存在,如果不存在就创建一个
if(!targetFile.exists()){
targetFile.mkdirs();
}
try {
// 将上传文件存储到服务器中
//使用此方法保存必须要绝对路径且文件夹必须已存在,否则报错
attach.transferTo(targetFile);
} catch (Exception e) {
e.printStackTrace();
return "redirect:/scxz/rd?error=error1";
}
downloadLink = request.getContextPath()+"/statics/img/"+resourceFileName;
resourceLocPath = path+File.separator+resourceFileName;
}else{
String resourceName = null;
resourcePicPath = "../../statics/img/" +"bzd.jpg";
try {
//资源文件名字
resourceName = resourceDownload.getResourceName();
} catch (Exception e) {
e.printStackTrace();
}
resourceFileName = resourceName + "."+prefix;
File targetFile = new File(path,resourceFileName);
if(!targetFile.exists()){
targetFile.mkdirs();
}
try {
attach.transferTo(targetFile);
} catch (Exception e) {
e.printStackTrace();
return "redirect:/scxz/rd?error=error1";
}
downloadLink = request.getContextPath()+"/statics/img/"+resourceFileName;
resourceLocPath = path+File.separator+resourceFileName;
}
// 发布者的手机号
resourceDownload.setResourceUserPhone(((UserInfo)session.getAttribute(Constants.QT_USER_SESSION)).getUserPhone());
// 发布者的昵称
resourceDownload.setResourceUserName(((UserInfo)session.getAttribute(Constants.QT_USER_SESSION)).getUserName());
//new日期对象
Date date = new Date();
//转换提日期输出格式
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 发布的日期
resourceDownload.setResourcePublishDate(dateFormat.format(date));
// 资源下载的链接(虚拟)
resourceDownload.setResourceDownloadLink(downloadLink);
// 资源的下载链接(真实)
resourceDownload.setResourceLocPath(resourceLocPath);
// 资源的名字
resourceDownload.setResourceName(resourceFileName);
// 资源的图片(虚拟)
resourceDownload.setResourcePicPath(resourcePicPath);
// 资源的状态(1:待审核)
resourceDownload.setResourceStatus(1);
try {
// 上传资源
if(resourceDownloadService.uploadResources(resourceDownload)){
return "redirect:/scxz/rd";
}
} catch (Exception e) {
e.printStackTrace();
}
return "redirect:/scxz/rd";
}
return "redirect:/scxz/rd?error=error2";
}
测试一下,上传这些文件
上传成功,没问题
再测试一下这部分
然后发现当我上传的是0KB的文件,!attach.isEmpty()为false,然后我就加了判断
// 上传资源报错的提示
if(null != fileUploadError && fileUploadError.equals("error1")){
fileUploadError = Constants.FILEUPLOAD_ERROR_1;
}else if(null != fileUploadError && fileUploadError.equals("error2")){
fileUploadError = Constants.FILEUPLOAD_ERROR_2;
}
model.addAttribute("fileUploadError",fileUploadError);
public final static String FILEUPLOAD_ERROR_1 = " * 上传失败!";
public final static String FILEUPLOAD_ERROR_2 = " * 上传文件要大于0KB!";
上传基本没问题了
接下来是翻页的功能,这里的form[2]是这个jsp的第三个form
function page_nav(frm,num){
frm.pageIndex.value = num;
frm.submit();
}
对应的form里面还有
这样分页就完成了
然后是分类,在上面的form基础上加上条件
资源的显示这部分功能就完成了,控制层代码:
//资源库
// 这里的error是上传资源有错误的提示
@RequestMapping(value="rd")
public String rd(Model model, HttpSession session,
@RequestParam(value="resourceName",required=false) String resourceName,
@RequestParam(value="resourceOneSort",required=false) String _resourceOneSort,
@RequestParam(value="resourceTwoSort",required=false) String _resourceTwoSort,
@RequestParam(value="resourceThreeSort",required=false) String _resourceThreeSort,
@RequestParam(value="pageIndex",required=false) String pageIndex,
@RequestParam(value="error",required= false)String fileUploadError){
List resourceDownloads = null; //资源信息列表
//列出一级分类列表,注:二级和三级分类列表通过异步ajax获取(文章的分类和资源分类共用的)
List categoryLevel1List = null;
List categoryLevel2List = null;
List categoryLevel3List = null;
//资源页面容量(5)
int pageSize = Constants.pageSize;
//当前页码(1)
Integer currentPageNo = 1;
//判断pageIndex是否为空
if(pageIndex != null){
try{
//Integer.valueOf(),返回一个int,毕竟前面定义pageIndex是一个String,而currentPageNo是Integer
//如果当前页有其他值,就把1改为那个值,没有就默认第一页开始
currentPageNo = Integer.valueOf(pageIndex);
//数字格式化错误,如果你的参数字符串不是数字的话,经过Integer.valueOf(argument)就会抛出
// NumberFormatException异常。表示将字符串解析成int类型数字出现异常。
}catch (NumberFormatException e) {
e.printStackTrace();
}
}
Integer resourceOneSort = null;
if(_resourceOneSort != null && !_resourceOneSort.equals("")){
resourceOneSort = Integer.parseInt(_resourceOneSort);
}
Integer resourceTwoSort = null;
if(_resourceTwoSort != null && !_resourceTwoSort.equals("")){
resourceTwoSort = Integer.parseInt(_resourceTwoSort);
}
Integer resourceThreeSort = null;
if(_resourceThreeSort != null && !_resourceThreeSort.equals("")){
resourceThreeSort = Integer.parseInt(_resourceThreeSort);
}
//总数量(表)
int totalCount = 0;
try {
totalCount = resourceDownloadService.getResourceInfoCount(resourceName, resourceOneSort, resourceTwoSort, resourceThreeSort,3);
} catch (Exception e) {
e.printStackTrace();
}
//总页数
PageSupport pages = new PageSupport();
pages.setCurrentPageNo(currentPageNo); //当前页码
pages.setPageSize(pageSize); //页码容量
pages.setTotalCount(totalCount); //页码条数
int totalPageCount = pages.getTotalPageCount(); //计算出一共多少页
//控制首页和尾页
// if(currentPageNo < 1){
// currentPageNo = 1;
// }else if(currentPageNo > totalPageCount){
// currentPageNo = totalPageCount;
// }
try {
resourceDownloads = resourceDownloadService.getResourceInfoList(resourceName, resourceOneSort, resourceTwoSort,resourceThreeSort,3,currentPageNo, pageSize);
/*拿到一级的id,分类编码,分类名称*/
categoryLevel1List = articleCategoryService.getArticleCategoryListByParentId(null);
} catch (Exception e) {
e.printStackTrace();
}
model.addAttribute("resourceDownloads", resourceDownloads);
model.addAttribute("categoryLevel1List", categoryLevel1List);
model.addAttribute("pages", pages);
model.addAttribute("resourceName", resourceName);
model.addAttribute("resourceOneSort", resourceOneSort);
model.addAttribute("resourceTwoSort", resourceTwoSort);
model.addAttribute("resourceThreeSort", resourceThreeSort);
//二级分类列表和三级分类列表---回显
if(resourceTwoSort != null && !resourceTwoSort.equals("")){
categoryLevel2List = getCategoryList(resourceOneSort.toString());
model.addAttribute("categoryLevel2List", categoryLevel2List);
}
if(resourceThreeSort != null && !resourceThreeSort.equals("")){
categoryLevel3List = getCategoryList(resourceTwoSort.toString());
model.addAttribute("categoryLevel3List", categoryLevel3List);
}
// 上传资源报错的提示
if(null != fileUploadError && fileUploadError.equals("error1")){
fileUploadError = Constants.FILEUPLOAD_ERROR_1;
}else if(null != fileUploadError && fileUploadError.equals("error2")){
fileUploadError = Constants.FILEUPLOAD_ERROR_2;
}
model.addAttribute("fileUploadError",fileUploadError);
//跳到资源库
return "qt/resourceDownload";
}
download_info 下载表
字段名称 |
类型 |
是否主键 |
是否为空 |
字段描述 |
Id |
bigInt(30) |
Y |
N |
id |
resourceId |
bigInt(30) |
N |
N |
资源id |
userPhone |
varchar(20) |
N |
N |
下载的用户 |
resourceIdUserPhone |
varchar(20) |
N |
N |
资源的上传者 |
resourceName |
varchar(30) |
N |
N |
资源名字 |
resourcePicPath |
varchar(500) |
N |
N |
资源图片虚拟路径 |
resourceDownloadDate |
datetime |
N |
N |
资源的下载时间 |
CREATE TABLE download_info(
id BIGINT(30) NOT NULL AUTO_INCREMENT,
resourceId BIGINT(30) NOT NULL,
userPhone VARCHAR(20) NOT NULL,
resourceIdUserPhone VARCHAR(20) NOT NULL,
resourceName VARCHAR(30) NOT NULL,
resourcePicPath VARCHAR(500) NOT NULL,
resourceDownloadDate DATETIME NOT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
下载资源使用的是模态框
在这里我遇到一个问题,数据是通过遍历出来的,按钮是在里面的,模态框内容也不能放出去,要不值拿不到
于是就产生一个尴尬的问题,点击哪个按钮显示的都是第一个资源的信息
我改成class,那点击就是五个模态框信息,这样是不合理的
解决方法是不去设置具体的值,而是根据遍历出来的id去显示,这样就没问题了,因为id是唯一的
接着,写js的时候出现了个bug
org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'resourcePicPath' is not present
是因为我加了“”,导致后面几个参数没数据了,去掉就行
在这还遇到一个暂时解决不了的问题,扣除积分后无法自动下载,js写的有问题,后续再看看
后台页面>
medal_info 勋章表
字段名称 |
类型 |
是否主键 |
是否为空 |
字段描述 |
Id |
bigInt(30) |
Y |
N |
id |
userPhone |
varchar(20) |
N |
N |
勋章上传者手机号 |
medalPicPath |
varchar(500) |
N |
N |
勋章虚拟路径 |
medalLocPath |
varchar(500) |
N |
N |
勋章真实路径 |
medalTitle |
Varchar(30) |
N |
Y |
勋章提示语 |
medalPublishDate |
datetime |
N |
N |
勋章的发布日期 |
medalStatus |
int |
N |
N |
勋章的状态 |
勋章的状态:1:使用中;2:停止使用
CREATE TABLE medal_info(
id BIGINT(30) NOT NULL AUTO_INCREMENT,
userPhone VARCHAR(20) NOT NULL,
medalPicPath VARCHAR(500) NOT NULL,
medalLocPath VARCHAR(500) NOT NULL,
medalTitle VARCHAR(30) NOT NULL,
medalPublishDate DATETIME NOT NULL,
medalStatus INT NOT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
现在写一下批量操作的功能,类似的都可以这样写
int batchDelMedal(Integer[] ids);
/* 全选 */
$("table thead th input:checkbox").on("click", function() {
$(this).closest("table").find("tr > td:first-child input:checkbox").prop("checked", $("table thead th input:checkbox").prop("checked"));
});
<%--批量删除--%>
function commonreuslt(data) {
if (data.code == "1000") {
// icon参数为1是成功的图标,2000是显示那个弹窗的毫秒数
layer.msg(data.msg, {
icon : 1,
time : 2000
}, function() {
//获取窗口索引
var index = parent.layer.getFrameIndex(window.name);
if (index > 0) {
// 父页面刷新
//parent.location.reload();
// parent.location.href:上一层页面跳转;parent.location.replace:实现父页面刷新
//parent.location.href: http://localhost:8080/ht/medalList
parent.location.replace(parent.location.href)
//关闭弹出的子页面窗口
parent.layer.close(index);
} else {
// index是undefined,在这里执行了代码刷新了页面
// location.href:当前页完整url
location.replace(location.href)
//location.reload();
}
});
} else if (data.code == "1001") {
// 操作失败
layer.msg(data.msg, {
icon : 2,
time : 2000
});
} else if (data.code == "1002") {
alert("亲,操作失败!")
}
}
delete from medal_info where id in
#{id}
写js的时候,报了个错误
js的let报错Let definitions are not supported by current JavaScript version
解决方法:在IDE中设置JavaScript版本。可以在Windows上使用ctrl + alt + s进行访问。至少需要使用ECMA Script 6才能使用let
。
在写资源下载的时候,遇到一些问题,虽然解决了,但是写的很菜
游客无法下载,要有登录提醒
积分不够显示充值通道
假如我下载资源后,后退连接再下载,无法下载,会提醒用户页面过期,要刷新
假如我有12积分,去了一个1积分的资源,不下载,再去一个12积分的资源,下载后我就没有积分了,但是后退到那个1积分下载页面,点击下载,我积分就变成了11积分(不合理,要处理,无法下载,会提醒用户页面过期,要刷新)
假如我有12积分,打开谷歌浏览器去了一个12积分的资源页面,又打开火狐浏览器,去了一个1积分资源的页面,先在谷歌下载,我就没积分了,又去火狐下载,我的积分就变成了11(不合理,账号禁止多开)--暂时写成禁止使用该功能,要去刷新或重新登录
假如我在下载资源的页面清了session的信息,那这样是下载不了的,要提示用户重新登录
还有就是用户在下载资源过程中,发布者也在删除资源,这个也要处理一下
用户在下载资源之前(已经打开资源详情页),发布者先删除资源,用户下载,应该无法下载,有错误提示,不会扣除积分等操作
用户在下载资源过程中,发布者删除资源,不能删除,提醒发布者有用户在下载他的资源,提示稍后删除
效果大概就这样了:
完成每日签到的功能
按照自己理解写的,只能写成这样了
$(document).ready(function() {
// 签到
$(".signIn").on("click",function(){
var obj = $(this);
// 签到检查:0已签到;1未签到
var signInFlag = obj.attr("signInFlag");
if(signInFlag != "0"){
$.ajax({
type:"GET",
url:"signIn.json",
data:{},
dataType:"json",
success:function(data){
if(data.result == "true"){//签到成功
// window.location.href="index";
$(".si1").hide();
$("#si2").show();
alert("亲,签到成功,获得1积分,明天继续加油呀");
}else if(data.result == "false2"){//已经签到
alert("亲,页面过时请刷新");
}else if(data.result == "false1"){
alert("亲,网络异常,无法签到");
}
},
error:function(data){
alert("亲,网络异常,无法签到");
}
});
}else {
alert("亲,您今天已经签到了,明天再来吧")
}
});
})
//签到
@RequestMapping(value="/signIn.json")
@ResponseBody
public Object signIn(HttpSession session){
HashMap resultMap = new HashMap();
// 用户签到字段
String signIn = ((UserInfo)session.getAttribute(Constants.QT_USER_SESSION)).getSignIn();
// 用户手机
String userPhone = ((UserInfo)session.getAttribute(Constants.QT_USER_SESSION)).getUserPhone();
CalendarGet calendarGet = new CalendarGet();
String webUrl="http://www.baidu.com";
//获取当前网络时间(百度时间)
String webTime=calendarGet.getNetworkTime(webUrl);
if (webTime.equals("")){
// 无法获取网络时间,网络异常
resultMap.put("result", "false1");
return JSONArray.toJSONString(resultMap);
}
if (signIn.equals(webTime)){
// 时间一样,说明当天已经签到
resultMap.put("result", "false2");
}
if (!signIn.equals(webTime)){
boolean a = false;
try {
// 更新签到字段的日期,以年月日显示yyyy-MM-dd(同时也增加积分userValue=userValue+1, )
a = qtUserInfoService.updateUserInfo(userPhone,null,webTime);
if (a){
UserInfo userInfo = null;
// 更新sessiion(后面怎么刷新网页都是显示已签到),也防止倒退页面刷积分
userInfo = qtUserInfoService.getUserInfo(null,((UserInfo)session.getAttribute(Constants.QT_USER_SESSION)).getUserPhone());
// 签到成功
resultMap.put("result", "true");
// 传1回去显示已关注样式(因为第一次显示是通过js显示的)
session.setAttribute("signInFlag","1");
session.setAttribute(Constants.QT_USER_SESSION, userInfo);
}
else {
// 中途有其他问题显示网络异常
resultMap.put("result", "false1");
}
} catch (Exception e) {
e.printStackTrace();
}
}
return JSONArray.toJSONString(resultMap);
}
/*
* 获取当前网络时间
*/
public static String getNetworkTime(String webUrl) {
try {
URL url=new URL(webUrl); // 取得资源对象
URLConnection conn=url.openConnection(); // 生成连接对象
conn.connect(); // 发出连接
long dateL=conn.getDate(); // 读取网站日期时间
Date date=new Date(dateL); // 转换为标准时间对象
SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd");
return dateFormat.format(date);
}catch (MalformedURLException e) {
e.printStackTrace();
}catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
System.out.println("网络异常");
}
return "";
}
主态个人空间
发布文章这一块太大了,显的好难看,于是用折叠把它隐藏掉
点击文案可以打开,再点击可以收回去
主态的文章详情页
(1)在主页或者其他页面点击文章的评论按钮能进入文章详情页,因为评论输入框在文章详情底部,如果文章巨长,我还要不停下滑页面才能评论,这显的用户体验感很差,于是就要对地址做一个定位,很简单
在文章详情页评论模块给个id,在主页的评论按钮a标签最后给个#评论模块的id,这样就可以了(如#pldw)
(2)在文章详情页时候,图片样式有问题,改成了这样显示就行了(外面的部分只是简单模仿头像框,忽略吧...)
更换头像
这样鼠标指在头像部分就会出现更换头像的蒙层,移开就消失,只有主态的文章详情页才有更换头像蒙层
https://blog.csdn.net/qq_36850813/article/details/89711784