蓝天筹项目开发记录
项目功能分析
-
做这个小程序的本意是,我曾经参加过我家乡的志愿者活动,然后加入的志愿者组织是家乡独自成立的一支
有着上千名成员的志愿者团队。因为名为蓝天高凉志愿服务队,所以起了名字叫蓝天筹,希望能做出一个为家乡服务的小程序。
首页显示的功能:显示所有(由蓝天志愿队的会长或部长发起的众筹项目,这样确保都是经过组织上鉴定和实地考察帮助者的真实性)
首页有根据不同类型的排序功能,比如根据不同众筹项目的类型,孤寡老人,贫困学生,留守儿童等。
还有根据众筹项目的进展进度排序,有未完成,即将完成,已完成,已结束。(根据当前众筹金额与目标筹集金额做比例运算,而动态修改类型)
有根据目标筹集金额的高低排序
首页具有上拉到底加载更多的功能。
底部导航栏的第二个为添加项目(设计为只能通过管理员账号登陆实现,确保项目的真实性,必须由志愿者组织发起)
添加项目详情页则填写一些帮助者的信息,详情,上传相关图片。
首页里点击具体项目,能跳转项目详情页,能查看项目和帮助者的信息,还能查看照片。
在详情页具有我要帮帮他的按钮,(设计为模拟捐款和留言的功能)
筹集人的微信头像和昵称,还有众筹金额,留言都会显示在详情页。
技术选型:
- ssm框架
- mysql
- linux作为服务器
- 前端是微信小程序
项目流程设计
1. 界面设计
1.1 添加筹集项目的管理员登陆页
1.1.1 后端用salt+password的方式校验和存储密码
加密:
public class passwordencryptor { //这是一个自定义的hexdigits,如果黑客不知道这串东西,是不能穷举破解出来的,知道salt也没用 private final static string[] hexdigits = {"0", "1", "2", "3", "4", "5", "6", "!", "#", "@", "a", "b", "c", "d", "*", "f", "g", "f"}; private string salt; //salt private string algorithm; //散列算法 public passwordencryptor(string salt,string algorithm) { this.salt = salt; this.algorithm = algorithm; } //加密 public string encode(string rawpassword){ try { messagedigest digest = messagedigest.getinstance(algorithm); return bytearraytohex(digest.digest(mergepasswordandsalt(rawpassword).getbytes("utf-8"))); } catch (nosuchalgorithmexception e) { e.printstacktrace(); } catch (unsupportedencodingexception e) { e.printstacktrace(); } return null; } //合并salt + password private string mergepasswordandsalt(string rawpassword){ if(rawpassword==null){ rawpassword = ""; } if(salt.equals("")||salt==null){ return rawpassword; }else{ return rawpassword+"{"+salt+"}"; //用特殊的方式拼接salt } } /** * 字节数组转16进制 */ private static string bytearraytohex(byte[] b){ stringbuffer stringbuffer = new stringbuffer(); for(int i=0;i<b.length;i++){ stringbuffer.append(bytetohex(b[i])); } return stringbuffer.tostring(); } private static string bytetohex(byte b){ int n = b; if(n<0){ n+=256; } int d1 = n / hexdigits.length; int d2 = n % hexdigits.length; return hexdigits[d1]+hexdigits[d2]; } //初始的管理员密码 public static void main(string[] args) { string salt = uuid.randomuuid().tostring(); passwordencryptor encodermd5 = new passwordencryptor(salt, "sha-256"); string encodedpassword = encodermd5.encode("csyzhanpeng123456"); system.out.println("加密后密码:" + encodedpassword + "\n密码长度:" + encodedpassword.length()); system.out.println("salt:" + salt); } }
前后端校验
@service public class userpasswordserviceimpl implements userpasswordservice { @autowired private sysadminmapper sysadminmapper; @override public boolean isvalid(string username, string password) { sysadminexample example = new sysadminexample(); example.or().andusernameequalto(username); list<sysadmin> admins = sysadminmapper.selectbyexample(example); sysadmin sysadmin = new sysadmin(); //说明找到了这个username,后面就是检测密码 if(admins!=null&&admins.size()!=0){ sysadmin = admins.get(0); //校验 passwordencryptor encryptor = new passwordencryptor(sysadmin.getsalt(), "sha-256"); string encodepassword = encryptor.encode(password); if(encodepassword.equals(sysadmin.getpassword())){ return true; }else{ return false; } }else{ return false; } } }
salt+password参考链接:
前端通过存在本地缓存,缓存管理员的登陆态。
通过在page的onshow生命周期,通过判断缓存,来达到拦截页面(检测是否具有权限)
onshow:function(e){ let that = this; wx.getstorage({ key: 'login_key', success: function(res) { wx.request({ url: baseurl + 'item/itemtypes', method: "get", success: function (res) { console.log(res); that.setdata({ itemtypes: res.data.extend.itemtypes }) } }) }, fail:function(){ wx.navigateto({ url: '../login/login', }) } }) },
formsubmit: function (e) { wx.showloading({ title: '登录中...', }) console.log(e); this.setdata({ disabled: true }); wx.request({ url: baseurl+"login", method:"post", data: { username: e.detail.value.no, password: e.detail.value.pwd }, header: { 'content-type': 'application/x-www-form-urlencoded' }, success: function (res) { console.log(res); if (res.data.code == 200) { // 设置本地缓存 wx.setstoragesync('login_key', res.data.extend.login_key); wx.showtoast({ title: "管理员登录成功", icon: 'success', duration: 2000 }) settimeout(function () { wx.switchtab({ url: '../add/add', }) }, 2000) } else { wx.showtoast({ title: "用户名或密码错误", icon: 'none', duration: 2000 }) } } }) },
1.2 登陆后个人信息填写页
1.3 筹款项目首页展示
1.3.1 轮播图(仅宣传用)
1.3.2 多选下拉菜单的实现
功能:三级级联菜单 项目类型 + 项目进度情况 + 按目标筹集金额从低到高(从高到低)
总结:
-
下拉小按钮
/* 这里的icon用border渲染出来,而不是用字体图标 */ .icon{ margin-left: 10rpx; margin-top: 16rpx; display: inline-block; border: 10rpx solid transparent; border-top: 10rpx solid #666; }
效果:
参考链接:
-
下拉多选菜单
思路:用大view包小view,通过点击状态和 标志记录位来 打开(使下拉菜单显示), 当选中其中一个或者再点
一次按钮时,将会切换打开状态或者关闭下拉菜单
核心:切换 ,记录打开状态位,通过点击事件进行状态位切换(这里的状态位为下拉导航索引)。前端根据状态位判断是否渲染出来
data:{ //筹集项目列表 items:[], // 筹集项目类型 itemtypes:[], // 进度类型 processtypes:[], //排序类型 sorttypes:[ { id: 1, sorttypename:"按目标金额从低到高"}, { id: 2, sorttypename: "按目标金额从高到低"} ], // 选中的项目类型 selectitemtype:-1, // 选中的进度类型 selectprocesstype:-1, // 选中的排序类型 selectsorttype:-1, // 显示下拉菜单导航索引 shownavindex:0, // 各类型菜单打开(滑动))状态 itemtypeopen:false, processopen:false, sortopen:false
这里只写其中的项目下拉菜单的显示
下拉菜单触发按钮
<view class="nav-child" bindtap='listitemtype' data-nav="1"> <view class='content'>项目类型</view> <view class="icon"></view> </view>
下拉菜单条目
<view class="itemtypemenu {{itemtypeopen? 'slidown':'slidup'}}" wx:if='{{shownavindex==1}}' wx:for-index="index"> <view class="itemtype" bindtap='selectitemtype' data-itemtypeid='-1'> 不限 </view> <view class="itemtype {{selectitemtype==(index+1)?'highlight':''}}" wx:for="{{itemtypes}}" wx:for-item="itemtype" wx:key="itemtypeid" data-itemtypeid="{{itemtype.itemtypeid}}" bindtap='selectitemtype' > {{itemtype.itemtypename}} </view> </view>
点击事件的处理逻辑:
listitemtype:function(e){ console.log(this.data.itemtypeopen) // 如果已经打开了,再按一次就是关闭。 if (this.data.itemtypeopen){ this.setdata({ itemtypeopen: false, shownavindex:0 }) }else{ this.setdata({ itemtypeopen: true, //切换 要显示的菜单导航 shownavindex:e.currenttarget.dataset.nav }) } },
选中事件的处理逻辑:
selectitemtype: function (e) { console.log(e.currenttarget) // 注意;这里的data-传过来的属性会自动换成小写 let id = e.currenttarget.dataset.itemtypeid; if (id == -1) { this.setdata({ // 不限,也就是提交的这个筛选条件为空 selectitemtype: -1, itemtypeopen: false, shownavindex: 0 }) } else { this.setdata({ selectitemtype: id, itemtypeopen: false, shownavindex: 0 }) } let that = this; // 找出符合条件的items that.request_finditem(that); },
根据条件,查询符合的项目
// 封装一个根据条件查询得请求函数 request_finditem:function(that){ wx.request({ url: baseurl + 'item/finditems', data: { itemtype: that.data.selectitemtype, itemprocesstype: that.data.selectprocesstype, sorttype: that.data.selectsorttype }, method: "get", success: function (res) { that.setdata({ items: res.data.extend.items }) } }) }
总结:
- css就不贴出来了,我选择了调一下margin,让菜单条目对应菜单导肮,以及选中高亮
选中高亮的实现原理:判断id与列表渲染的索引index是否匹配。如果匹配了就渲染高亮class
<view class="itemtype {{selectitemtype==(index+1)?'highlight':''}}" wx:for="{{itemtypes}}" wx:for-item="itemtype" wx:key="itemtypeid" data-itemtypeid="{{itemtype.itemtypeid}}" bindtap='selectitemtype' >
-
不设置筛选条件,菜单条目为 不限,通过设置为-1来让后端根据-1 做条件的判断
@override public list<raiseitem> getitemsbytype(finditemdto dto) { raiseitemexample example = new raiseitemexample(); raiseitemexample.criteria criteria = example.or(); integer itemtype = dto.getitemtype(); integer itemprocesstype = dto.getitemprocesstype(); if(itemtype!=-1){ criteria.anditemtypeequalto(itemtype); } if(itemprocesstype!=-1){ criteria.anditemprocesstypeequalto(itemprocesstype); } integer sorttype = dto.getsorttype(); if(sorttype!=-1){ if(sorttype.equals(1)){ example.setorderbyclause("raise_target asc"); }else if(sorttype.equals(2)){ example.setorderbyclause("raise_target desc"); } } return raiseitemmapper.selectbyexample(example); }
效果演示:
1.3.3 列表每个条目的设计
1.3.4 上拉加载更多的分页实现
参考链接:
思路 :
后端返回分页数据
-
小程序:
-
加载更多组件
正在加载
```
/* 上拉加载更多 */ .weui-loading { margin: 0 5px; width: 20px; height: 20px; display: inline-block; vertical-align: middle; -webkit-animation: weuiloading 1s steps(12, end) infinite; animation: weuiloading 1s steps(12, end) infinite; /* base64的格式 */ background: transparent url(data:image/svg+xml;base64,phn2zyb4bwxucz0iahr0cdovl3d3dy53my5vcmcvmjawmc9zdmciihdpzhropsixmjaiighlawdodd0imtiwiib2awv3qm94psiwidagmtawidewmci+phbhdgggzmlsbd0ibm9uzsigzd0ittagmggxmdb2mtawsdb6ii8+phjly3qgd2lkdgg9ijciighlawdodd0imjaiihg9ijq2ljuiihk9ijqwiibmawxspsijrtlfouu5iibyed0insigcnk9ijuiihryyw5zzm9ybt0idhjhbnnsyxrlkdagltmwksivpjxyzwn0ihdpzhropsi3iibozwlnahq9ijiwiib4psi0ni41iib5psi0mcigzmlsbd0iizk4oty5nyigcng9ijuiihj5psi1iib0cmfuc2zvcm09injvdgf0zsgzmcaxmduuotggnjupii8+phjly3qgd2lkdgg9ijciighlawdodd0imjaiihg9ijq2ljuiihk9ijqwiibmawxspsijoui5otlbiibyed0insigcnk9ijuiihryyw5zzm9ybt0icm90yxrlkdywidc1ljk4idy1ksivpjxyzwn0ihdpzhropsi3iibozwlnahq9ijiwiib4psi0ni41iib5psi0mcigzmlsbd0ii0ezqtfbmiigcng9ijuiihj5psi1iib0cmfuc2zvcm09injvdgf0zsg5mca2nsa2nskilz48cmvjdcb3awr0ad0inyigagvpz2h0psiymciged0indyunsiget0indaiigzpbgw9iinbqke5queiihj4psi1iibyet0insigdhjhbnnmb3jtpsjyb3rhdguomtiwidu4ljy2idy1ksivpjxyzwn0ihdpzhropsi3iibozwlnahq9ijiwiib4psi0ni41iib5psi0mcigzmlsbd0ii0iyqjjcmiigcng9ijuiihj5psi1iib0cmfuc2zvcm09injvdgf0zsgxntagntqumdignjupii8+phjly3qgd2lkdgg9ijciighlawdodd0imjaiihg9ijq2ljuiihk9ijqwiibmawxspsijqkfcoei5iibyed0insigcnk9ijuiihryyw5zzm9ybt0icm90yxrlkde4mca1mca2nskilz48cmvjdcb3awr0ad0inyigagvpz2h0psiymciged0indyunsiget0indaiigzpbgw9iindmkmwqzeiihj4psi1iibyet0insigdhjhbnnmb3jtpsjyb3rhdguolte1mca0ns45oca2nskilz48cmvjdcb3awr0ad0inyigagvpz2h0psiymciged0indyunsiget0indaiigzpbgw9iindqkncq0iiihj4psi1iibyet0insigdhjhbnnmb3jtpsjyb3rhdguolteymca0ms4znca2nskilz48cmvjdcb3awr0ad0inyigagvpz2h0psiymciged0indyunsiget0indaiigzpbgw9iinemkqyrdiiihj4psi1iibyet0insigdhjhbnnmb3jtpsjyb3rhdguoltkwidm1idy1ksivpjxyzwn0ihdpzhropsi3iibozwlnahq9ijiwiib4psi0ni41iib5psi0mcigzmlsbd0ii0rbrefeqsigcng9ijuiihj5psi1iib0cmfuc2zvcm09injvdgf0zsgtnjagmjqumdignjupii8+phjly3qgd2lkdgg9ijciighlawdodd0imjaiihg9ijq2ljuiihk9ijqwiibmawxspsijrtjfmkuyiibyed0insigcnk9ijuiihryyw5zzm9ybt0icm90yxrlkc0zmcatns45oca2nskilz48l3n2zz4=) no-repeat; background-size: 100%; } .weui-loadmore { width: 65%; margin: 1.5em auto; line-height: 1.6em; font-size: 14px; text-align: center; } .weui-loadmore__tips { display: inline-block; vertical-align: middle; }
- 微信小程序 自带的触底函数
onreachbottom:function(){ let that = this; // 模拟延时,显示加载更多 // wx.request({ // url: baseurl+'', // }) // that.setdata({ // }) let islastpage = that.data.pageinfo.islastpage; // 不是最后一页,才要请求分页 if (!islastpage){ settimeout(() => { // 判断一下这个触底是常规触底,还是带着条件的触底事件 let f1 = that.data.selectitemtype; let f2 = that.data.selectprocesstype; let f3 = that.data.selectsorttype; if (f1 != -1 || f2!=-1 || f3!=-1){ // 带条件查询 (其实带条件和不带条件其实在后端可以合并为一个接口的)) wx.request({ url: baseurl + 'item/finditems', data: { itemtype: that.data.selectitemtype, itemprocesstype: that.data.selectprocesstype, sorttype: that.data.selectsorttype, pn: that.data.pageinfo.pagenum + 1 }, method: "get", success: function (res) { let olditems = that.data.items; let newitems = res.data.extend.pageinfo.list; let pageinfo = res.data.extend.pageinfo; // concat拼接后返回一个新的数组 newitems = olditems.concat(newitems); that.setdata({ pageinfo: pageinfo, items: newitems, ishideloadmore: pageinfo.islastpage ? true : false }) } }) }else{ // 不带条件查询 wx.request({ url: baseurl + 'item/all', data: { pn: that.data.pageinfo.pagenum + 1 }, method: "get", success: function (res) { let olditems = that.data.items; let newitems = res.data.extend.pageinfo.list; // concat拼接后返回一个新的数组 newitems = olditems.concat(newitems); let pageinfo = res.data.extend.pageinfo; that.setdata({ pageinfo: pageinfo, items: newitems, ishideloadmore: pageinfo.islastpage ? true : false }) } }) } }, 1000); } }
-
-
后端返回分页
// 获取所有的项目基本信息 @requestmapping(value = "/all",method = requestmethod.get) @responsebody public msg getallitems(@requestparam(value = "pn",defaultvalue = "1") integer page_num){ pagehelper.startpage(page_num,5); list<raiseitem> items = raiseitemservice.getallitems(); pageinfo<raiseitem> pageinfo = new pageinfo<>(items); return msg.success().add("pageinfo",pageinfo); }
// 根据类型来查询符合的项目 @requestmapping(value = "/finditems",method = requestmethod.get) @responsebody public msg finditems(@requestparam(value = "pn",defaultvalue = "1") integer page_num,finditemdto dto){ // 开始分页 pagehelper.startpage(page_num,5); list<raiseitem> items = raiseitemservice.getitemsbytype(dto); pageinfo<raiseitem> pageinfo = new pageinfo<>(items); return msg.success().add("pageinfo",pageinfo); }
效果:
加载完成后:
1.3.5 新增筹款项目
普通表单ui
文件(图片)上传ui
-
长按图片删除的操作
参考链接:
参考js语法:
splice() 方法向/从数组中添加/删除项目,然后返回被删除的项目。
注释:该方法会改变原始数组。
语法
arrayobject.splice(index,howmany,item1,.....,itemx)
参数 描述 index 必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。 howmany 必需。要删除的项目数量。如果设置为 0,则不会删除项目。 item1, ..., itemx 可选。向数组添加的新项目。 返回值
类型 描述 array 包含被删除项目的新数组,如果有的话。 data: { data: [{id:0,value:'a',name:'a' },{id:1,value:'b',name:'b' }], index: 0, currentid },
对象数组的下拉菜单的使用
<picker class="picker" bindchange="bindchange" value="{{index}}" range="{{data}}" range-key="name"> <view > 当前选择:{{data[index].name}} </view> </picker>
itemtypepickerchange:function(e){ this.setdata({ index:e.detail.value }) },
1.4 筹款项目详情页
-
编写基础的wxml,wxss,js,获取item表和detail表的基础信息,布局用flex嵌套flex合理布局
效果如下:
-
这个按钮悬浮固定在底部的实现:
```css
/* 按钮固定高为46px */
.detail_box{
margin-bottom: 92rpx;
}/* 固定在底部,这样可以避免内容区的内容过多,让按钮一直看不到 */
.chou_button{
z-index: 999; position: fixed; bottom: 0; width: 100%; } ```
1.4.1 模仿样式
1.4.2 项目进展(含进度条,时间线+图片+留言)
1.4.3 参与筹款人的名单+留言
效果:
<!-- 筹集人的名单列表 --> <view class="raise_person_box"> <view class="raise_person_title"> 捐助人名单 </view> <view wx:for="{{persons}}" wx:for-key="person.item_person_id" wx:for-item="person" class="raise_person_item"> <view class="raise_person_item_left"> <view class="index_pic"> <image src="{{person.useravatarurl}}"></image> </view> </view> <view class="raise_person_item_right"> <view class="raise_person_item_right_top"> {{person.usernickname}} </view> <view class="raise_person_item_right_mid"> 支持了: <text class="mid_money">{{person.raisemoney}} 元</text> </view> <view class="raise_person_item_right_bottom"> {{person.comment}} </view> <view class="raise_person_item_right_time"> {{person.raisetime}} </view> </view> </view> </view>
总结:html页面用了flex嵌套布局吧
js部分:onshow()用于获取捐助人名单+留言信息
wx.request({ url: baseurl + 'item/person', data:{ itemid: that.data.itemid, }, method: "get", success: function (res) { //调用 处理留言时间的函数,修改返回的数据 let list = res.data.extend.pageinfo.list; for (let i=0;i<list.length;i++){ let last_time = timehandle(list[i].raisetime) list[i].raisetime = last_time; } that.setdata({ persons: list, }) } })
此处要提的是一个特殊的常用需求:就是根据返回的时间戳计算出几天前,几个月前,又或者是具体的月份,年份
js部分:用了一个专门的函数放在单独的js文件,放在utils目录下,被其他的js import引入使用
function commenttimehandle(datestr) { // datestr = 2018-09-06 18:47:00" 测试时间 //获取datastr的秒数 打印结果--1536230820000 var publishtime = datestr / 1000, date = new date(publishtime * 1000), //获取datestr的标准格式 console.log(date) 打印结果 thu sep 06 2018 18:47:00 gmt+0800 (中国标准时间) // 获取date 中的 年 月 日 时 分 秒 y = date.getfullyear(), m = date.getmonth() + 1, d = date.getdate(), h = date.gethours(), m = date.getminutes(), s = date.getseconds(); // 对 月 日 时 分 秒 小于10时, 加0显示 例如: 09-09 09:01 if (m < 10) { m = '0' + m; } if (d < 10) { d = '0' + d; } if (h < 10) { h = '0' + h; } if (m < 10) { m = '0' + m; } if (s < 10) { s = '0' + s; } // console.log("年", y); // 年 2018 // console.log("月", m); // 月 09 // console.log("日", d); // 日 06 // console.log("时", h); // 时 18 // console.log("分", m); // 分 47 // console.log("秒", s); // 秒 00 //获取此时此刻日期的秒数 var nowtime = new date().gettime() / 1000, diffvalue = nowtime - publishtime, // 获取此时 秒数 与 要处理的日期秒数 之间的差值 // 一天86400秒 获取相差的天数 取整 diff_days = parseint(diffvalue / 86400), // 一时3600秒 diff_hours = parseint(diffvalue / 3600), diff_minutes = parseint(diffvalue / 60), diff_secodes = parseint(diffvalue); if (diff_days > 0 && diff_days < 3) { //相差天数 0 < diff_days < 3 时, 直接返出 return diff_days + "天前"; } else if (diff_days <= 0 && diff_hours > 0) { return diff_hours + "小时前"; } else if (diff_hours <= 0 && diff_minutes > 0) { return diff_minutes + "分钟前"; } else if (diff_secodes < 60) { if (diff_secodes <= 0) { return "刚刚"; } else { return diff_secodes + "秒前"; } } else if (diff_days >= 3 && diff_days < 30) { return m + '-' + d + ' ' + h + ':' + m; } else if (diff_days >= 30) { return y + '-' + m + '-' + d + ' ' + h + ':' + m; } } module.exports = { timehandle: commenttimehandle }
如何使用:在js里引入这个js文件的函数
import { timehandle } from '../../utils/timehandle';
分页后端:
// 获取筹集人列表 @requestmapping(value = "/person",method = requestmethod.get) @responsebody public msg getpersons(@requestparam(value = "pn",defaultvalue = "1")integer page_num, integer itemid){ pagehelper.startpage(page_num,5); list<raiseitemperson> persons = raiseitemservice.getraisepersons(itemid); pageinfo<raiseitemperson> pageinfo = new pageinfo<>(persons); return msg.success().add("pageinfo",pageinfo); }
1.4.4 参与众筹的按钮(涉及到微信支付,暂时无法完成。可以模拟)
1.4.4.1 获取微信用户id,头像,昵称
总结:
- 通过微信最新官方文档,用button标签,设置open-type属性,然后绑定指定的事件,可以在js中
获取到用户头像,昵称 (可在一个按钮绑定两个事件,一个用来获取用户信息,一个用来发出请求)
<button open-type='getuserinfo' type='primary' bindgetuserinfo="bindgetuserinfo" bindtap='donate'>我要帮帮他</button>
//获取用户信息 bindgetuserinfo: function (e) { console.log(e.detail.userinfo) this.setdata({ usernickname: e.detail.userinfo.nickname, useravatarurl: e.detail.userinfo.avatarurl }) }
1.4.4.2 模拟支付页面的模态框
效果:
总结:就是通过按钮点击切换模态框的显示,然后在模态框里模拟微信支付功能以及添加留言
<!-- modal支付模态框 --> <modal id="modal" hidden="{{hiddenmodal}}" title="支付页面" confirm-text="确定" cancel-text="取消" bindcancel="cancel" bindconfirm="confirm"> <text style="font-weight:bolder;font-size:35rpx">捐助金额:</text> <input type='text' placeholder="请填写资助金额" class='weui-input' bindinput="bindkeyinput" auto-focus/> <text style="font-weight:bolder;font-size:35rpx">留言:</text> <input type='text' placeholder="留言" class='weui-input brief_description' bindinput="bindkeycomment"></input> </modal>
confirm:function(){ let openid = getapp().globaldata.openid; console.log("openid: " + openid); let that = this; wx.request({ url: baseurl+'item/donate', data:{ donate_money: that.data.donate_money, itemid: that.data.itemid, comment: that.data.comment, openid: openid, usernickname:that.data.usernickname, useravatarurl: that.data.useravatarurl }, method:"post", header:{ "content-type": "application/x-www-form-urlencoded" }, success:function(res){ that.setdata({ comment: "", donate_money: "", hiddenmodal: true, hiddenmodal:true }) // 发起请求 wx.request({ url: baseurl + 'item/detail', data: { itemid: that.data.itemid }, success: function (res) { if (res.data.code == 200) { that.setdata({ currenttarget: res.data.extend.detail.currenttarget, raisepersonnum: res.data.extend.detail.raisepersonnum, }) } } }) } }) }
cancel:function(){ this.setdata({ donate_money:"", comment:"", hiddenmodal: true, }) }, //此处省略其他input的处理事件
1.5 新增筹款项目填写页
总结:
// 发送首页图片。对应首页图片的处理 wx.uploadfile({ url: baseurl + 'item/imageindex', filepath: files[0], name: 'img', header:{ 'content-type':'application/json' }, success: function (res) { console.log("res: "+res.data); // 微信小程序 uploadfile的坑是接收的是json字符串,不会帮你自动转js对象。所以需要自己解析data let data = json.parse(res.data); //获取返回值 that.setdata({ server_file_index: data.extend.file_index_path }) } })
点击提交按钮,图片如何处理
-
分成两个接口,一个是首页图片,另一个是详情多个图片urls.把文件名存放在数据库中
add_submit:function(){ let that = this; let item_index = that.data.index; // 先上传图片,后端处理成功后(通过返回值包含了首页图片路径, //以及多个图片展示的路径)回调进行insert let files = that.data.files; // 发送首页图片。对应首页图片的处理 wx.uploadfile({ url: baseurl + 'item/imageindex', filepath: files[0], name: 'img', header:{ 'content-type':'application/json' }, success: function (res) { console.log("res: " + res.data); // 微信小程序 uploadfile的坑是接收的是json字符串,不会帮你自动转js对象。所以需要自己解析data let data = json.parse(res.data); //获取返回值 that.setdata({ server_file_index: data.extend.file_index_path }) //等server_file_index成功获取后再执行下面的add操作 let i; //循环发送多个详情的图片 for (i = 1; i < files.length; i++) { // 采用闭包,保证索引值正确 (function (i) { //调用promise处理异步 that.getimage(i, that).then((index) => { //最后一张处理完成 if (that.data.server_detail_files.length == (that.data.files.length - 1)) { console.log("开始执行提交add"); console.log("index: " + index); console.log("server_detail_file:" + that.data.server_detail_file); // 提交插入请求 wx.request({ url: baseurl + '/item/add', method: 'post', header: { "content-type": "application/x-www-form-urlencoded" }, data: { targetperson: name, itemdescription: description, raisetarget: money, itemtype: that.data.itemtypes[that.data.index].itemtypeid, createtime: date, description: detail_description, picindexurl: that.data.server_file_index, picdetailurls: that.data.server_detail_files.join(',') }, success: function (res) { if (res.data.code == 200) { // 清空 that.setdata({ targetperson: "", itemdescription: "", raisetarget: "", index: 0, date: "", detail_description: "", server_file_index: "", server_detail_files: "", files: "" }) wx.switchtab({ url: '/pages/index/index', }) } } }) } }); })(i) } } }
基础补习之闭包:
因为for循环,的索引index不会从1,2,3这样,而是执行完了,显示最后一个索引值。需要闭包控制一下。
演示:
文本测试:
18岁花季少女突发心脏病。急需救助!
小红成绩优异,家里经济贫困,在石鼓镇。父母残疾,只能在家里下田。小红下课后就回家做饭做菜给他们吃,自己暑假出去打工赚学费。学校老师说她的成绩非常好,是年级前三的学生,模拟成绩很可能考上211学校。
该案例已经过蓝天志愿组织实地考察,经多名志愿者核实,情况属实。希望大家能给予帮助,奉献大爱。
1. 微信小程序 uploadfile的坑是接收的是json字符串,不会帮你自动转js对象。所以需要自己解析data
-
for循环里有异步请求,想要for里面的异步请求都执行完再执行其他的怎么做?
参考链接:
异步请求:
// promise getimage:function(i,that){ console.log("当前循环:"+i); return new promise(function (resolve, reject) { wx.uploadfile({ url: baseurl + '/item/images', filepath: that.data.files[i], name: 'img', success: (res) => { // console.log("这是第"+i+"次循环") console.log(that.data); //先拿到旧的 var server_detail_files = that.data.server_detail_files; console.log("server_detail_files" + server_detail_files); //服务端返回的 let data = json.parse(res.data); let files_detail_path = data.extend.files_detail_path; console.log("files_detail_path:" + files_detail_path) //如果是拼的第一个,加入数组 console.log("server_detail_files:" + server_detail_files) //push是在原数组上操作,并返回新的长度。 server_detail_files.push(files_detail_path); //获取返回值 that.setdata({ server_detail_files: server_detail_files }) resolve(server_detail_files); } }) }) }
for循环里异步,并且通过判断i==要执行下一步的值去执行add请求
//循环发送多个详情的图片 for (i = 1; i < files.length; i++) { // 采用闭包,保证索引值正确 (function (i) { //调用promise处理异步 that.getimage(i,that).then(()=>{ //最后一张处理完成 console.log("i: "+i); //在then里判断是否是最后一张图片,从而达到完成所有的for循环后再执行这个提交插入的请求 if (that.data.server_detail_files.length == (that.data.files.length-1)) { // 提交插入请求 wx.request({ url: baseurl + '/item/add', method: 'post', header:{ "content-type":"application/x-www-form-urlencoded" }, data: { targetperson: name, itemdescription: description, raisetarget: money, itemtype: that.data.itemtypes[that.data.index].itemtypeid, createtime: date, description: detail_description, picindexurl: that.data.server_file_index, picdetailurls: that.data.server_detail_files.join(',') }, success: function (res) { if (res.code == 200) { // 清空 that.setdata({ targetperson: "", itemdescription: "", raisetarget: "", index: 0, date: "", detail_description: "", server_file_index: "", server_detail_files: "", files: "" }) wx.navigateto({ url: 'pages/index/index', }) return; } } }) } }); })(i) }
如果微信小程序使用post请求,后端没数据的话,说明小程序没有设置header为
header: { 'content-type': 'application/json' },
2. 需求分析
3. 数据库设计
powerdesigner的使用
安装:
3.1 筹款项目表
sys_admin:
id | username | password | salt |
---|---|---|---|
1 | zhanp | @gd5@a6#ca1f5b@30@3a@2bcc#5f0b0f40@f@5a6@1!a4a5b6b0f1#b1!0a1cfa2 | d4171b48-fca9-45b1-9bb7-716ea057aa25 |
raise_item:
item_id | target_person | raise_target | current_target | raise_person_num | pic_index_url | item_description | item_type_id | item_process_type_id |
---|---|---|---|---|---|---|---|---|
1 | 小江 | 5000 | 1000 | 60 | http://localhost/image/po1.jpg | xxxx加油,战胜病魔 | 1 | 1 |
2 | 小洋 | 6000 | 2000 | 70 | xxx加油,努力读书 | 2 | 2 |
3.2 筹集项目进度类型表
item_process_type
item_process_type_id | item_process_type_name |
---|---|
1 | 未完成 |
2 | 即将完成 |
3 | 已完成 |
4 | 已结束 |
(已结束是时间已过,该项目取消筹款了)
3.3 筹集项目类型表
raise_item_type
item_type_id | item_type_name |
---|---|
1 | 孤寡老人 |
2 | 贫困学生 |
3 | 留守儿童 |
4 | 患病在身 |
5 | 其他 |
3.4 筹集项目详情表
raise_item_detail
item_detail_id | item_id | description | pic_detail_urls | create_time |
---|---|---|---|---|
3.5 筹集项目进展表
raise_item_process (一个项目可以有多次进展)
item_process_id | item_id | pic_process_urls | description | |
---|---|---|---|---|
1 | 1 | |||
2 | 1 |
3.6 筹集项目捐助人表
raise_item_person
item_person_id | item_id | user_avatar_url | user_nick_name | raise_money | comment | raise_time | open_id |
---|---|---|---|---|---|---|---|
后续还有排行榜
4. 后台设计
4.1 用户管理
4.2 角色管理
4.3 权限管理
5. 接口编写
项目编写流程
1. mysql数据库的准备
2. ssm环境搭建
单元测试模拟数据的过程中遇到的bug
<!--在spring单元测试中,由于引入validator而导致的tomcat7及以下的el表达式版本不一致--> <dependency> <groupid>org.apache.tomcat</groupid> <artifactid>tomcat-el-api</artifactid> <version>8.5.24</version> <scope>provided</scope> </dependency> <dependency> <groupid>org.apache.tomcat</groupid> <artifactid>tomcat-jasper-el</artifactid> <version>8.5.24</version> <scope>provided</scope> </dependency>
报错:
解决方法:因为@responsebody,但是底层的jackson忘记引入了
<!--jackson支持--> <dependency> <groupid>com.fasterxml.jackson.core</groupid> <artifactid>jackson-databind</artifactid> <version>2.9.8</version> </dependency>
3. 日志
<context-param> <param-name>log4jconfiglocation</param-name> <param-value>classpath*:log/log4j.properties</param-value> </context-param> <listener> <description>log4j</description> <listener-class>org.springframework.web.util.log4jconfiglistener</listener-class> </listener>
3.小程序的错误提示
一定要仔细看报错的部分,会显示哪一行报错,不要自己瞎找。不然改一天你都不知道哪里错。
指示add.js 221行错了
测试:
部署问题
为什么服务器端的mysql一直连不上去?
因为root只允许localhost访问,所以要修改。
别忘了flush一下
成功:
前言
今天在服务器安装mysql
之后,登录发现密码错误,但是我没有设置密码呀,最后百度之后得知,mysql
在5.7版本之后会自动创建一个初始密码。
报错如下:
[root@mytestlnx02 ~]# mysql -u root -p enter password: error 1045 (28000): access denied for user 'root'@'localhost' (using password: yes)
修改密码
1. 检查mysql
服务是否启动,如果启动,关闭mysql
服务
//查看mysql服务状态 [root@mytestlnx02 ~]# ps -ef | grep -i mysql root 22972 1 0 14:18 pts/0 00:00:00 /bin/sh /usr/bin/mysqld_safe --datadir=/var/lib/mysql --socket=/var/lib/mysql/mysql.sock --pid-file=/var/run/mysqld/mysqld.pid --basedir=/usr --user=mysql mysql 23166 22972 0 14:18 pts/0 00:00:00 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib/mysql/plugin --user=mysql --log-error=/var/log/mysqld.log --pid-file=/var/run/mysqld/mysqld.pid --socket=/var/lib/mysql/mysql.sock root 23237 21825 0 14:22 pts/0 00:00:00 grep -i mysql //关闭服务 [root@mytestlnx02 ~]# service mysql stop [root@mytestlnx02 ~]#
2. 修改mysql
的配置文件my.cnf
my.cnf`配置文件的位置,一般在`/etc/my.cnf`,有些版本在`/etc/mysql/my.cnf
在配置文件中,增加2行代码
[mysqld] skip-grant-tables
作用是登录mysql
的时候跳过密码验证
然后启动mysql
服务,并进入mysql
[root@mytestlnx02 ~]# service mysqld start [root@mytestlnx02 ~]# [root@mytestlnx02 ~]# mysql -u root type 'help;' or '\h' for help. type '\c' to clear the current input statement. mysql>
3. 修改密码
连接mysql
这个数据库,修改用户密码
mysql> use mysql; reading table information for completion of table and column names you can turn off this feature to get a quicker startup with -a database changed mysql> update mysql.user set authentication_string=password('root_password') where user='root'; query ok, 1 row affected, 1 warning (0.00 sec) rows matched: 1 changed: 1 warnings: 1 mysql> flush privileges; query ok, 0 rows affected (0.00 sec) mysql> exit
4. 重启mysql
服务
先将之前加在配置文件里面的2句代码注释或删除掉,然后重启mysql
服务,就可以使用刚刚设置的密码登录了。
[root@mytestlnx02 ~]# service mysql start [root@mytestlnx02 ~]# [root@mytestlnx02 ~]# mysql -u root -p enter password: welcome to the mysql monitor. commands end with ; or \g.
p.s.
在centos
上的操作方式有所不同。
执行修改密码的命令一直报错
mysql> update user set authentication_string=password('xxxxxxxx') where user='root'; error 1064 (42000): you have an error in your sql syntax; check the manual that corresponds to your mysql server version for the right syntax to use near '('root_password') where user='root'' at line 1
不可能是语法问题,检查了很多遍,最后发现centos
下应该这样操作:
查看初始密码
[root@vm_0_8_centos ~]# grep 'temporary password' /var/log/mysqld.log 2018-09-26t04:25:54.927944z 5 [note] [my-010454] [server] a temporary password is generated for root@localhost: dn34n/=?aifz
可以看到初始密码为dn34n/=?aifz
使用初始密码登录
[root@vm_0_8_centos ~]# mysql -u root -p enter password: welcome to the mysql monitor. commands end with ; or \g. your mysql connection id is 8 server version: 8.0.12 mysql community server - gpl copyright (c) 2000, 2018, oracle and/or its affiliates. all rights reserved.
修改密码
mysql> alter user 'root' identified by 'xxxxxxxxx'; error 1820 (hy000): you must reset your password using alter user statement before executing this statement. mysql> alter user 'root'@'localhost' identified by 'xxxxxxxx'; query ok, 0 rows affected (0.11 sec) mysql> flush privileges; query ok, 0 rows affected (0.01 sec) mysql> exit bye
重启服务就生效了
[root@vm_0_8_centos ~]# service mysqld stop redirecting to /bin/systemctl stop mysqld.service [root@vm_0_8_centos ~]# service mysqld start redirecting to /bin/systemctl start mysqld.service
navicat导出数据库文件
部署到服务器上的mysql连接参数
jdbc.driver=com.mysql.jdbc.driver jdbc.url=jdbc:mysql://xxxx/sky_chou?useunicode=true&characterencoding=utf-8 jdbc.username=root jdbc.password=xxxxx
记住:一定不要添加usessl=true这种配置信息,不然会sql报错
改成localhost,不然不会识别服务器的ip。!
升级到https
通过nginx升级到https
- 要去购买的云服务器上下载ssl证书
- 把nginx的ssl证书复制到linux服务器上的nginx的conf目录下
-
修改nginx.conf文件
#https server server { listen 443; server_name 你的域名; ssl on; ssl_certificate xxxx_bundle.crt; ssl_certificate_key xxx.key; ssl_session_timeout 5m; ssl_protocols tlsv1 tlsv1.1 tlsv1.2; ssl_ciphers ecdhe-rsa-aes128-gcm-sha256:high:!anull:!md5:!rc4:!dhe; ssl_prefer_server_ciphers on; location / { client_max_body_size 16m; client_body_buffer_size 128k; proxy_pass http://127.0.0.1:9999/; proxy_set_header host $host; proxy_set_header x-real-ip $remote_addr; proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for; proxy_set_header x-forwarded-proto https; proxy_next_upstream off; proxy_connect_timeout 30; proxy_read_timeout 300; proxy_send_timeout 300; } }
要着重修改的ssl相关地方:
ssl on; ssl_certificate xxxxx.crt; ssl_certificate_key xxxx.key;
这些是网上的固定配置
proxy_set_header host $host; proxy_set_header x-real-ip $remote_addr; proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for; proxy_set_header x-forwarded-proto https; proxy_next_upstream off; proxy_connect_timeout 30; proxy_read_timeout 300; proxy_send_timeout 300;
nginx代理443端口的配置部分
server { listen 443; server_name 你的域名; location / { client_max_body_size 16m; client_body_buffer_size 128k; proxy_pass http://127.0.0.1:9999/;
记住所有的server模块的配置都应该包含在http块里面,不然会报错的!
成功标志:
linux上运行多个tomcat
- 修改环境变量:一般的都是/etc/profile
- 加入以下代码(tomcat路径要配置自己实际的tomcat安装目录)
4.保存退出。
5.再输入:source /etc/profilecond tomcat在生效
6.第一个tomcat,保持解压后的原状不用修改,
来到第二个tomcat的bin目录下打开catalina.sh ,找到下面红字,
# os specific support. $var must be set to either true or false.
在下面增加如下代码
export catalina_base=$catalina_2_base
export catalina_home=$catalina_2_home
7.来到第二个tomcat的conf目录下
打开server.xml更改端口:
修改server.xml配置和第一个不同的启动、关闭监听端口。
8.分别进入两个tomcat的bin目录,启动tomcat--./startup.sh 9.然后访问 和 都可以看到熟悉的tomcat欢迎界面
修改后示例如下:
<connector port="9080" maxhttpheadersize="8192" 端口:8080->9080
maxthreads="150" minsparethreads="25" maxsparethreads="75"
enablelookups="false" redirectport="8443" acceptcount="100"
connectiontimeout="20000" disableuploadtimeout="true" />
<connector port="9009" 端口:8009->9009
enablelookups="false" redirectport="8443" protocol="ajp/1.3" />
上一篇: 微信公众号开发:用户管理