欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

蓝天筹项目开发记录

程序员文章站 2022-03-20 08:02:53
项目功能分析 1. 做这个小程序的本意是,我曾经参加过我家乡的志愿者活动,然后加入的志愿者组织是家乡独自成立的一支 有着上千名成员的志愿者团队。因为名为蓝天高凉志愿服务队,所以起了名字叫蓝天筹,希望能做出一个为家乡服务的小程序。 2. 首页显示的功能:显示所有(由蓝天志愿队的会长或部长发起的众筹项目 ......

项目功能分析

  1. 做这个小程序的本意是,我曾经参加过我家乡的志愿者活动,然后加入的志愿者组织是家乡独自成立的一支

    有着上千名成员的志愿者团队。因为名为蓝天高凉志愿服务队,所以起了名字叫蓝天筹,希望能做出一个为家乡服务的小程序。

  2. 首页显示的功能:显示所有(由蓝天志愿队的会长或部长发起的众筹项目,这样确保都是经过组织上鉴定和实地考察帮助者的真实性)

  3. 首页有根据不同类型的排序功能,比如根据不同众筹项目的类型,孤寡老人,贫困学生,留守儿童等。

  4. 还有根据众筹项目的进展进度排序,有未完成,即将完成,已完成,已结束。(根据当前众筹金额与目标筹集金额做比例运算,而动态修改类型)

  5. 有根据目标筹集金额的高低排序

  6. 首页具有上拉到底加载更多的功能。

  7. 底部导航栏的第二个为添加项目(设计为只能通过管理员账号登陆实现,确保项目的真实性,必须由志愿者组织发起)

  8. 添加项目详情页则填写一些帮助者的信息,详情,上传相关图片。

  9. 首页里点击具体项目,能跳转项目详情页,能查看项目和帮助者的信息,还能查看照片。

  10. 在详情页具有我要帮帮他的按钮,(设计为模拟捐款和留言的功能)

  11. 筹集人的微信头像和昵称,还有众筹金额,留言都会显示在详情页。

技术选型

  1. ssm框架
  2. mysql
  3. linux作为服务器
  4. 前端是微信小程序

项目流程设计

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 多选下拉菜单的实现

功能:三级级联菜单 项目类型 + 项目进度情况 + 按目标筹集金额从低到高(从高到低)

总结:

  1. 下拉小按钮

    /* 这里的icon用border渲染出来,而不是用字体图标 */
    
    .icon{
      margin-left: 10rpx;
      margin-top: 16rpx;
      display: inline-block;
    
      border: 10rpx solid transparent;
      border-top: 10rpx solid #666;
    }

效果:

蓝天筹项目开发记录

参考链接:

蓝天筹项目开发记录

  1. 下拉多选菜单

    思路:用大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
            })
          }
        })
      }

    总结:

    1. 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来让后端根据-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 上拉加载更多的分页实现

参考链接:

思路 :

  1. 后端返回分页数据

  2. 小程序:

    1. 加载更多组件

      
      



      ​ ```

       /* 上拉加载更多 */
    
       .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;
       }
    
    1. 微信小程序 自带的触底函数
         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);
           }
         }
  3. 后端返回分页

    //    获取所有的项目基本信息
        @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 新增筹款项目

  1. 普通表单ui

  2. 文件(图片)上传ui

  3. 长按图片删除的操作

    参考链接:

    参考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 筹款项目详情页

  1. 编写基础的wxml,wxss,js,获取item表和detail表的基础信息,布局用flex嵌套flex合理布局

    效果如下:

蓝天筹项目开发记录

蓝天筹项目开发记录

  1. 这个按钮悬浮固定在底部的实现:

    ```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,头像,昵称

​ 总结:

  1. 通过微信最新官方文档,用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
        })
      }
    })

点击提交按钮,图片如何处理

  1. 分成两个接口,一个是首页图片,另一个是详情多个图片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

  1. 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)
          }
  2. 如果微信小程序使用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

蓝天筹项目开发记录

部署到服务器上的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

  1. 要去购买的云服务器上下载ssl证书

蓝天筹项目开发记录

  1. 把nginx的ssl证书复制到linux服务器上的nginx的conf目录下

蓝天筹项目开发记录

  1. 修改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

  1. 修改环境变量:一般的都是/etc/profile
  2. 加入以下代码(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配置和第一个不同的启动、关闭监听端口。
修改后示例如下:
    端口:8005->9005

​ <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" />

8.分别进入两个tomcat的bin目录,启动tomcat--./startup.sh

9.然后访问 和 都可以看到熟悉的tomcat欢迎界面

蓝天筹项目开发记录